From 17550c1a7d61b33eeb7fe3f20c42c3d288ee89c1 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Mon, 11 Aug 2025 08:49:46 +0800 Subject: [PATCH 1/6] Add support for v29.0 --- .github/workflows/ci_v28.0.yml | 2 +- .github/workflows/ci_v29.0.yml | 167 ++++++++ README.md | 6 +- docker-compose.yml | 4 +- lib/open_api_typesense/client.ex | 2 +- .../operations/conversations.ex | 10 - lib/open_api_typesense/operations/curation.ex | 1 + .../operations/nl_search_models.ex | 150 ++++++++ .../schemas/collection_response.ex | 3 + .../schemas/collection_schema.ex | 3 + .../schemas/collection_update_schema.ex | 6 +- .../schemas/multi_search_result_item.ex | 15 +- .../multi_search_searches_parameter.ex | 2 +- .../schemas/nl_search_model_create_schema.ex | 78 ++++ .../schemas/nl_search_model_delete_schema.ex | 18 + .../schemas/nl_search_model_schema.ex | 18 + .../schemas/search_parameters.ex | 6 + .../schemas/search_request_params.ex | 28 ++ .../search_request_params_voice_query.ex | 18 + .../schemas/search_result.ex | 15 +- .../schemas/search_result_hit.ex | 6 + .../search_result_hit_hybrid_search_info.ex | 18 + mix.exs | 2 +- priv/open_api.yml | 362 ++++++++++++++++-- test/operations/nl_search_models_test.exs | 95 +++++ test/operations/stemming_test.exs | 12 +- 26 files changed, 985 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/ci_v29.0.yml create mode 100644 lib/open_api_typesense/operations/nl_search_models.ex create mode 100644 lib/open_api_typesense/schemas/nl_search_model_create_schema.ex create mode 100644 lib/open_api_typesense/schemas/nl_search_model_delete_schema.ex create mode 100644 lib/open_api_typesense/schemas/nl_search_model_schema.ex create mode 100644 lib/open_api_typesense/schemas/search_request_params.ex create mode 100644 lib/open_api_typesense/schemas/search_request_params_voice_query.ex create mode 100644 lib/open_api_typesense/schemas/search_result_hit_hybrid_search_info.ex create mode 100644 test/operations/nl_search_models_test.exs diff --git a/.github/workflows/ci_v28.0.yml b/.github/workflows/ci_v28.0.yml index e90b490..1be96fc 100644 --- a/.github/workflows/ci_v28.0.yml +++ b/.github/workflows/ci_v28.0.yml @@ -36,7 +36,7 @@ jobs: - typesense: '28.0' otp: '27' elixir: '1.18' - lint: true + lint: false services: typesense: diff --git a/.github/workflows/ci_v29.0.yml b/.github/workflows/ci_v29.0.yml new file mode 100644 index 0000000..55ae5af --- /dev/null +++ b/.github/workflows/ci_v29.0.yml @@ -0,0 +1,167 @@ +name: CI v29.0 + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + # https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs + # Workflows that would otherwise be triggered using `on: push` or + # `on: pull_request` won't be triggered if you add any of the + # following strings to the commit message in a push, or the HEAD + # commit of a pull request: + # - [skip ci] + # - [ci skip] + # - [no ci] + # - [skip actions] + # - [actions skip] + + test: + runs-on: ubuntu-latest + + env: + MIX_ENV: test + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LATEST_TYPESENSE: '29.0' + + strategy: + matrix: + include: + - typesense: '29.0' + otp: '25' + elixir: '1.14' + lint: false + - typesense: '29.0' + otp: '28' + elixir: '1.18' + lint: true + + services: + typesense: + image: typesense/typesense:${{ matrix.typesense }} + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Check for misspellings + uses: codespell-project/actions-codespell@v2 + + - name: Start Typesense + run: | + docker run -id \ + -p 8108:8108 \ + --name typesense \ + -v /tmp/typesense-data:/data \ + -v /tmp/typesense-analytics-data:/analytics-data \ + typesense/typesense:${{ matrix.typesense}} \ + --api-key xyz \ + --data-dir /data \ + --enable-search-analytics=true \ + --analytics-dir=/analytics-data \ + --analytics-flush-interval=60 \ + --analytics-minute-rate-limit=100 \ + --enable-cors + + - name: Wait for Typesense to be healthy + shell: bash + run: | + start_time=$(date +%s) + timeout=30 + counter=0 + until curl -s http://localhost:8108/health | grep -q '"ok":true'; do + if [ $counter -eq $timeout ]; then + echo "Timed out waiting for Typesense to be healthy" + exit 1 + fi + echo "Waiting for Typesense to be healthy..." + sleep 1 + counter=$((counter + 1)) + done + end_time=$(date +%s) + elapsed=$((end_time - start_time)) + echo "Typesense healthcheck elapsed: ${elapsed}s" + + - name: Setup Elixir/OTP + uses: erlef/setup-beam@v1 + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + + - name: Cache dependencies/builds + uses: actions/cache@v4 + with: + path: | + deps + _build + key: ${{ runner.os }}-typesense-${{ matrix.typesense}}-${{ matrix.otp}}-${{ matrix.elixir}}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-typesense-${{ matrix.typesense}}-${{ matrix.otp }}-${{ matrix.elixir }}-mix- + + - name: Install Dependencies + run: | + mix local.rebar --if-missing + mix local.hex --if-missing + mix deps.get + + - name: Find unused dependencies + run: mix deps.unlock --check-unused + if: ${{ matrix.lint }} + + - name: Check retired dependencies + run: mix hex.audit + if: ${{ matrix.lint }} + + - name: Security audit of dependencies + run: mix deps.audit + if: ${{ matrix.lint }} + + - name: Compile project + run: mix compile --all-warnings + + - name: Run static analysis + run: mix credo --all --strict + if: ${{ matrix.lint }} + + - name: Check format files + run: mix format --check-formatted + if: ${{ matrix.lint }} + + - name: Restore PLT cache + id: plt_cache + uses: actions/cache/restore@v4 + with: + key: | + plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}- + path: | + priv/plts + if: ${{ matrix.lint }} + + - name: Create PLTs + if: ${{ steps.plt_cache.outputs.cache-hit != 'true' && matrix.lint }} + run: mix dialyzer --plt + + - name: Save PLT cache + id: plt_cache_save + uses: actions/cache/save@v4 + if: ${{ steps.plt_cache.outputs.cache-hit != 'true' && matrix.lint }} + with: + key: | + plt-${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-${{ hashFiles('**/mix.lock') }} + path: | + priv/plts + + - name: Dialyzer + run: mix dialyzer --format github --format dialyxir + if: ${{ matrix.lint }} + + - name: Run tests + run: mix test --only ${{ matrix.typesense }}:true --trace + + - name: Post test coverage to Coveralls + run: mix coveralls.github + if: ${{ matrix.lint && github.event_name == 'push' && github.ref == 'refs/heads/main' }} diff --git a/README.md b/README.md index cdf5ec8..fe4fc14 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Restful client for Typesense with adherence to Open API spec 3 (formerly Swagger [![Codacy Badge](https://app.codacy.com/project/badge/Grade/965dd3f8866d49c3b3e82edd0f6270cb)](https://app.codacy.com/gh/jaeyson/open_api_typesense/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![codescenene Average Code Health](https://codescene.io/projects/63240/status-badges/average-code-health)](https://codescene.io/projects/63240) +[![CI v29.0](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v29.0.yml/badge.svg)](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v29.0.yml) [![CI v28.0](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v28.0.yml/badge.svg)](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v28.0.yml) [![CI v27.1](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v27.1.yml/badge.svg)](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v27.1.yml) [![CI v27.0](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v27.0.yml/badge.svg)](https://github.com/jaeyson/open_api_typesense/actions/workflows/ci_v27.0.yml) @@ -32,6 +33,9 @@ Collections.get_collections(conn: conn) # another way (v1) opts = [limit: 1, conn: conn] Collections.get_collections(opts) + +# or (v1) +Collections.get_collections(limit: 1, conn: conn) ``` ## Installation @@ -41,7 +45,7 @@ by adding `open_api_typesense` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:open_api_typesense, "~> 1.0"} + {:open_api_typesense, "~> 1.1"} # Or from GitHub repository, if you want the latest greatest from main branch {:open_api_typesense, git: "https://github.com/jaeyson/open_api_typesense.git"} diff --git a/docker-compose.yml b/docker-compose.yml index d672867..b922d20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: typesense: - image: docker.io/typesense/typesense:28.0 + image: docker.io/typesense/typesense:29.0 container_name: typesense restart: on-failure ports: @@ -18,7 +18,7 @@ services: --analytics-minute-rate-limit=100 --enable-cors typesense_dashboard: - image: ghcr.io/bfritscher/typesense-dashboard:1.9.3 + image: ghcr.io/bfritscher/typesense-dashboard:2.1.0 container_name: typesense_dashboard restart: on-failure ports: diff --git a/lib/open_api_typesense/client.ex b/lib/open_api_typesense/client.ex index e203e1b..b0f78b3 100644 --- a/lib/open_api_typesense/client.ex +++ b/lib/open_api_typesense/client.ex @@ -156,7 +156,7 @@ defmodule OpenApiTypesense.Client do {:ok, []} end - defp parse_body(_code, [{mod, _t}], body) when is_binary(body) do + defp parse_body(_code, [{mod, _t}], body) do {:ok, Poison.decode!(body, as: [mod.__struct__()])} end diff --git a/lib/open_api_typesense/operations/conversations.ex b/lib/open_api_typesense/operations/conversations.ex index 2c02883..0d93c52 100644 --- a/lib/open_api_typesense/operations/conversations.ex +++ b/lib/open_api_typesense/operations/conversations.ex @@ -8,8 +8,6 @@ defmodule OpenApiTypesense.Conversations do @default_client OpenApiTypesense.Client @doc """ - post `/conversations/models` - Create a Conversation Model """ @doc since: "0.4.0" @@ -39,8 +37,6 @@ defmodule OpenApiTypesense.Conversations do end @doc """ - Delete a conversation model - Delete a conversation model """ @doc since: "0.4.0" @@ -65,8 +61,6 @@ defmodule OpenApiTypesense.Conversations do end @doc """ - List all conversation models - Retrieve all conversation models """ @doc since: "0.4.0" @@ -90,8 +84,6 @@ defmodule OpenApiTypesense.Conversations do end @doc """ - Retrieve a conversation model - Retrieve a conversation model """ @doc since: "0.4.0" @@ -116,8 +108,6 @@ defmodule OpenApiTypesense.Conversations do end @doc """ - Update a conversation model - Update a conversation model """ @doc since: "0.4.0" diff --git a/lib/open_api_typesense/operations/curation.ex b/lib/open_api_typesense/operations/curation.ex index d19768d..3b415c2 100644 --- a/lib/open_api_typesense/operations/curation.ex +++ b/lib/open_api_typesense/operations/curation.ex @@ -92,6 +92,7 @@ defmodule OpenApiTypesense.Curation do request: [{"application/json", {OpenApiTypesense.SearchOverrideSchema, :t}}], response: [ {200, {OpenApiTypesense.SearchOverride, :t}}, + {400, {OpenApiTypesense.ApiResponse, :t}}, {401, {OpenApiTypesense.ApiResponse, :t}}, {404, {OpenApiTypesense.ApiResponse, :t}} ], diff --git a/lib/open_api_typesense/operations/nl_search_models.ex b/lib/open_api_typesense/operations/nl_search_models.ex new file mode 100644 index 0000000..fb56e37 --- /dev/null +++ b/lib/open_api_typesense/operations/nl_search_models.ex @@ -0,0 +1,150 @@ +defmodule OpenApiTypesense.NlSearchModels do + @moduledoc since: "1.1.0" + + @moduledoc """ + Provides API endpoints related to nl search models + """ + + @default_client OpenApiTypesense.Client + + @doc """ + Create a NL search model + + Create a new NL search model. + """ + @doc since: "1.1.0" + @spec create_nl_search_model( + body :: OpenApiTypesense.NLSearchModelCreateSchema.t(), + opts :: keyword + ) :: + {:ok, OpenApiTypesense.NLSearchModelSchema.t()} + | {:error, OpenApiTypesense.ApiResponse.t()} + def create_nl_search_model(body, opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [body: body], + call: {OpenApiTypesense.NlSearchModels, :create_nl_search_model}, + url: "/nl_search_models", + body: body, + method: :post, + request: [{"application/json", {OpenApiTypesense.NLSearchModelCreateSchema, :t}}], + response: [ + {201, {OpenApiTypesense.NLSearchModelSchema, :t}}, + {400, {OpenApiTypesense.ApiResponse, :t}}, + {401, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end + + @doc """ + Delete a NL search model + + Delete a specific NL search model by its ID. + """ + @doc since: "1.1.0" + @spec delete_nl_search_model(model_id :: String.t(), opts :: keyword) :: + {:ok, OpenApiTypesense.NLSearchModelDeleteSchema.t()} + | {:error, OpenApiTypesense.ApiResponse.t()} + def delete_nl_search_model(model_id, opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [model_id: model_id], + call: {OpenApiTypesense.NlSearchModels, :delete_nl_search_model}, + url: "/nl_search_models/#{model_id}", + method: :delete, + response: [ + {200, {OpenApiTypesense.NLSearchModelDeleteSchema, :t}}, + {401, {OpenApiTypesense.ApiResponse, :t}}, + {404, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end + + @doc """ + List all NL search models + + Retrieve all NL search models. + """ + @doc since: "1.1.0" + @spec retrieve_all_nl_search_models(opts :: keyword) :: + {:ok, [OpenApiTypesense.NLSearchModelSchema.t()]} + | {:error, OpenApiTypesense.ApiResponse.t()} + def retrieve_all_nl_search_models(opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [], + call: {OpenApiTypesense.NlSearchModels, :retrieve_all_nl_search_models}, + url: "/nl_search_models", + method: :get, + response: [ + {200, [{OpenApiTypesense.NLSearchModelSchema, :t}]}, + {401, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end + + @doc """ + Retrieve a NL search model + + Retrieve a specific NL search model by its ID. + """ + @doc since: "1.1.0" + @spec retrieve_nl_search_model(model_id :: String.t(), opts :: keyword) :: + {:ok, OpenApiTypesense.NLSearchModelSchema.t()} + | {:error, OpenApiTypesense.ApiResponse.t()} + def retrieve_nl_search_model(model_id, opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [model_id: model_id], + call: {OpenApiTypesense.NlSearchModels, :retrieve_nl_search_model}, + url: "/nl_search_models/#{model_id}", + method: :get, + response: [ + {200, {OpenApiTypesense.NLSearchModelSchema, :t}}, + {401, {OpenApiTypesense.ApiResponse, :t}}, + {404, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end + + @doc """ + Update a NL search model + + Update an existing NL search model. + """ + @doc since: "1.1.0" + @spec update_nl_search_model( + model_id :: String.t(), + body :: OpenApiTypesense.NLSearchModelCreateSchema.t(), + opts :: keyword + ) :: + {:ok, OpenApiTypesense.NLSearchModelSchema.t()} + | {:error, OpenApiTypesense.ApiResponse.t()} + def update_nl_search_model(model_id, body, opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [model_id: model_id, body: body], + call: {OpenApiTypesense.NlSearchModels, :update_nl_search_model}, + url: "/nl_search_models/#{model_id}", + body: body, + method: :put, + request: [{"application/json", {OpenApiTypesense.NLSearchModelCreateSchema, :t}}], + response: [ + {200, {OpenApiTypesense.NLSearchModelSchema, :t}}, + {400, {OpenApiTypesense.ApiResponse, :t}}, + {401, {OpenApiTypesense.ApiResponse, :t}}, + {404, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end +end diff --git a/lib/open_api_typesense/schemas/collection_response.ex b/lib/open_api_typesense/schemas/collection_response.ex index 8716575..c3ecd81 100644 --- a/lib/open_api_typesense/schemas/collection_response.ex +++ b/lib/open_api_typesense/schemas/collection_response.ex @@ -9,6 +9,7 @@ defmodule OpenApiTypesense.CollectionResponse do default_sorting_field: String.t(), enable_nested_fields: boolean, fields: [OpenApiTypesense.Field.t()], + metadata: map, name: String.t(), num_documents: integer, symbols_to_index: [String.t()], @@ -19,6 +20,7 @@ defmodule OpenApiTypesense.CollectionResponse do defstruct [ :created_at, :fields, + :metadata, :name, :num_documents, default_sorting_field: "", @@ -74,6 +76,7 @@ defmodule OpenApiTypesense.CollectionResponse do default_sorting_field: {:string, :generic}, enable_nested_fields: :boolean, fields: [{OpenApiTypesense.Field, :t}], + metadata: :map, name: {:string, :generic}, num_documents: :integer, symbols_to_index: [string: :generic], diff --git a/lib/open_api_typesense/schemas/collection_schema.ex b/lib/open_api_typesense/schemas/collection_schema.ex index 118b926..2a6d84f 100644 --- a/lib/open_api_typesense/schemas/collection_schema.ex +++ b/lib/open_api_typesense/schemas/collection_schema.ex @@ -8,6 +8,7 @@ defmodule OpenApiTypesense.CollectionSchema do default_sorting_field: String.t(), enable_nested_fields: boolean, fields: [OpenApiTypesense.Field.t()], + metadata: map, name: String.t(), symbols_to_index: [String.t()], token_separators: [String.t()], @@ -16,6 +17,7 @@ defmodule OpenApiTypesense.CollectionSchema do defstruct [ :fields, + :metadata, :name, default_sorting_field: "", enable_nested_fields: false, @@ -69,6 +71,7 @@ defmodule OpenApiTypesense.CollectionSchema do default_sorting_field: {:string, :generic}, enable_nested_fields: :boolean, fields: [{OpenApiTypesense.Field, :t}], + metadata: :map, name: {:string, :generic}, symbols_to_index: [string: :generic], token_separators: [string: :generic], diff --git a/lib/open_api_typesense/schemas/collection_update_schema.ex b/lib/open_api_typesense/schemas/collection_update_schema.ex index 6e5f959..418f1c5 100644 --- a/lib/open_api_typesense/schemas/collection_update_schema.ex +++ b/lib/open_api_typesense/schemas/collection_update_schema.ex @@ -4,9 +4,9 @@ defmodule OpenApiTypesense.CollectionUpdateSchema do """ use OpenApiTypesense.Encoder - @type t :: %__MODULE__{fields: [OpenApiTypesense.Field.t()]} + @type t :: %__MODULE__{fields: [OpenApiTypesense.Field.t()], metadata: map} - defstruct [:fields] + defstruct [:fields, :metadata] defimpl(Poison.Decoder, for: OpenApiTypesense.CollectionUpdateSchema) do def decode(value, %{as: struct}) do @@ -49,6 +49,6 @@ defmodule OpenApiTypesense.CollectionUpdateSchema do def __fields__(type \\ :t) def __fields__(:t) do - [fields: [{OpenApiTypesense.Field, :t}]] + [fields: [{OpenApiTypesense.Field, :t}], metadata: :map] end end diff --git a/lib/open_api_typesense/schemas/multi_search_result_item.ex b/lib/open_api_typesense/schemas/multi_search_result_item.ex index 4fcb922..c42935a 100644 --- a/lib/open_api_typesense/schemas/multi_search_result_item.ex +++ b/lib/open_api_typesense/schemas/multi_search_result_item.ex @@ -15,9 +15,10 @@ defmodule OpenApiTypesense.MultiSearchResultItem do hits: [OpenApiTypesense.SearchResultHit.t()], out_of: integer, page: integer, - request_params: map, + request_params: OpenApiTypesense.SearchRequestParams.t(), search_cutoff: boolean, - search_time_ms: integer + search_time_ms: integer, + union_request_params: [OpenApiTypesense.SearchRequestParams.t()] } defstruct [ @@ -30,10 +31,11 @@ defmodule OpenApiTypesense.MultiSearchResultItem do :hits, :out_of, :page, - :request_params, :search_cutoff, :search_time_ms, - conversation: %OpenApiTypesense.SearchResultConversation{} + :union_request_params, + conversation: %OpenApiTypesense.SearchResultConversation{}, + request_params: %OpenApiTypesense.SearchRequestParams{} ] defimpl(Poison.Decoder, for: OpenApiTypesense.MultiSearchResultItem) do @@ -88,9 +90,10 @@ defmodule OpenApiTypesense.MultiSearchResultItem do hits: [{OpenApiTypesense.SearchResultHit, :t}], out_of: :integer, page: :integer, - request_params: :map, + request_params: {OpenApiTypesense.SearchRequestParams, :t}, search_cutoff: :boolean, - search_time_ms: :integer + search_time_ms: :integer, + union_request_params: [{OpenApiTypesense.SearchRequestParams, :t}] ] end end diff --git a/lib/open_api_typesense/schemas/multi_search_searches_parameter.ex b/lib/open_api_typesense/schemas/multi_search_searches_parameter.ex index 30822ca..cbbd6c5 100644 --- a/lib/open_api_typesense/schemas/multi_search_searches_parameter.ex +++ b/lib/open_api_typesense/schemas/multi_search_searches_parameter.ex @@ -9,7 +9,7 @@ defmodule OpenApiTypesense.MultiSearchSearchesParameter do union: boolean } - defstruct [:searches, :union] + defstruct [:searches, union: false] defimpl(Poison.Decoder, for: OpenApiTypesense.MultiSearchSearchesParameter) do def decode(value, %{as: struct}) do diff --git a/lib/open_api_typesense/schemas/nl_search_model_create_schema.ex b/lib/open_api_typesense/schemas/nl_search_model_create_schema.ex new file mode 100644 index 0000000..b6e37f9 --- /dev/null +++ b/lib/open_api_typesense/schemas/nl_search_model_create_schema.ex @@ -0,0 +1,78 @@ +defmodule OpenApiTypesense.NLSearchModelCreateSchema do + @moduledoc """ + Provides struct and type for a NLSearchModelCreateSchema + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + access_token: String.t(), + account_id: String.t(), + api_key: String.t(), + api_url: String.t(), + api_version: String.t(), + client_id: String.t(), + client_secret: String.t(), + id: String.t(), + max_bytes: integer, + max_output_tokens: integer, + model_name: String.t(), + project_id: String.t(), + refresh_token: String.t(), + region: String.t(), + stop_sequences: [String.t()], + system_prompt: String.t(), + temperature: number, + top_k: integer, + top_p: number + } + + defstruct [ + :access_token, + :account_id, + :api_key, + :api_url, + :api_version, + :client_id, + :client_secret, + :id, + :max_bytes, + :max_output_tokens, + :model_name, + :project_id, + :refresh_token, + :region, + :stop_sequences, + :system_prompt, + :temperature, + :top_k, + :top_p + ] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + access_token: {:string, :generic}, + account_id: {:string, :generic}, + api_key: {:string, :generic}, + api_url: {:string, :generic}, + api_version: {:string, :generic}, + client_id: {:string, :generic}, + client_secret: {:string, :generic}, + id: {:string, :generic}, + max_bytes: :integer, + max_output_tokens: :integer, + model_name: {:string, :generic}, + project_id: {:string, :generic}, + refresh_token: {:string, :generic}, + region: {:string, :generic}, + stop_sequences: [string: :generic], + system_prompt: {:string, :generic}, + temperature: :number, + top_k: :integer, + top_p: :number + ] + end +end diff --git a/lib/open_api_typesense/schemas/nl_search_model_delete_schema.ex b/lib/open_api_typesense/schemas/nl_search_model_delete_schema.ex new file mode 100644 index 0000000..6874d7f --- /dev/null +++ b/lib/open_api_typesense/schemas/nl_search_model_delete_schema.ex @@ -0,0 +1,18 @@ +defmodule OpenApiTypesense.NLSearchModelDeleteSchema do + @moduledoc """ + Provides struct and type for a NLSearchModelDeleteSchema + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{id: String.t()} + + defstruct [:id] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [id: {:string, :generic}] + end +end diff --git a/lib/open_api_typesense/schemas/nl_search_model_schema.ex b/lib/open_api_typesense/schemas/nl_search_model_schema.ex new file mode 100644 index 0000000..1f47896 --- /dev/null +++ b/lib/open_api_typesense/schemas/nl_search_model_schema.ex @@ -0,0 +1,18 @@ +defmodule OpenApiTypesense.NLSearchModelSchema do + @moduledoc """ + Provides struct and type for a NLSearchModelSchema + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{id: String.t()} + + defstruct [:id] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [id: {:string, :generic}] + end +end diff --git a/lib/open_api_typesense/schemas/search_parameters.ex b/lib/open_api_typesense/schemas/search_parameters.ex index 47f36bd..96bbf6c 100644 --- a/lib/open_api_typesense/schemas/search_parameters.ex +++ b/lib/open_api_typesense/schemas/search_parameters.ex @@ -43,6 +43,8 @@ defmodule OpenApiTypesense.SearchParameters do max_filter_by_candidates: integer, min_len_1typo: integer, min_len_2typo: integer, + nl_model_id: String.t(), + nl_query: boolean, num_typos: String.t(), offset: integer, override_tags: String.t(), @@ -109,6 +111,8 @@ defmodule OpenApiTypesense.SearchParameters do :max_filter_by_candidates, :min_len_1typo, :min_len_2typo, + :nl_model_id, + :nl_query, :num_typos, :offset, :override_tags, @@ -188,6 +192,8 @@ defmodule OpenApiTypesense.SearchParameters do max_filter_by_candidates: :integer, min_len_1typo: :integer, min_len_2typo: :integer, + nl_model_id: {:string, :generic}, + nl_query: :boolean, num_typos: {:string, :generic}, offset: :integer, override_tags: {:string, :generic}, diff --git a/lib/open_api_typesense/schemas/search_request_params.ex b/lib/open_api_typesense/schemas/search_request_params.ex new file mode 100644 index 0000000..cbd6e48 --- /dev/null +++ b/lib/open_api_typesense/schemas/search_request_params.ex @@ -0,0 +1,28 @@ +defmodule OpenApiTypesense.SearchRequestParams do + @moduledoc """ + Provides struct and type for a SearchRequestParams + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + collection_name: String.t(), + per_page: integer, + q: String.t(), + voice_query: OpenApiTypesense.SearchRequestParamsVoiceQuery.t() + } + + defstruct [:collection_name, :per_page, :q, :voice_query] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + collection_name: {:string, :generic}, + per_page: :integer, + q: {:string, :generic}, + voice_query: {OpenApiTypesense.SearchRequestParamsVoiceQuery, :t} + ] + end +end diff --git a/lib/open_api_typesense/schemas/search_request_params_voice_query.ex b/lib/open_api_typesense/schemas/search_request_params_voice_query.ex new file mode 100644 index 0000000..b552c12 --- /dev/null +++ b/lib/open_api_typesense/schemas/search_request_params_voice_query.ex @@ -0,0 +1,18 @@ +defmodule OpenApiTypesense.SearchRequestParamsVoiceQuery do + @moduledoc """ + Provides struct and type for a SearchRequestParamsVoiceQuery + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{transcribed_query: String.t()} + + defstruct [:transcribed_query] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [transcribed_query: {:string, :generic}] + end +end diff --git a/lib/open_api_typesense/schemas/search_result.ex b/lib/open_api_typesense/schemas/search_result.ex index f27d384..40a1714 100644 --- a/lib/open_api_typesense/schemas/search_result.ex +++ b/lib/open_api_typesense/schemas/search_result.ex @@ -13,9 +13,10 @@ defmodule OpenApiTypesense.SearchResult do hits: [OpenApiTypesense.SearchResultHit.t()], out_of: integer, page: integer, - request_params: map, + request_params: OpenApiTypesense.SearchRequestParams.t(), search_cutoff: boolean, - search_time_ms: integer + search_time_ms: integer, + union_request_params: [OpenApiTypesense.SearchRequestParams.t()] } defstruct [ @@ -26,10 +27,11 @@ defmodule OpenApiTypesense.SearchResult do :hits, :out_of, :page, - :request_params, :search_cutoff, :search_time_ms, - conversation: %OpenApiTypesense.SearchResultConversation{} + :union_request_params, + conversation: %OpenApiTypesense.SearchResultConversation{}, + request_params: %OpenApiTypesense.SearchRequestParams{} ] defimpl(Poison.Decoder, for: OpenApiTypesense.SearchResult) do @@ -82,9 +84,10 @@ defmodule OpenApiTypesense.SearchResult do hits: [{OpenApiTypesense.SearchResultHit, :t}], out_of: :integer, page: :integer, - request_params: :map, + request_params: {OpenApiTypesense.SearchRequestParams, :t}, search_cutoff: :boolean, - search_time_ms: :integer + search_time_ms: :integer, + union_request_params: [{OpenApiTypesense.SearchRequestParams, :t}] ] end end diff --git a/lib/open_api_typesense/schemas/search_result_hit.ex b/lib/open_api_typesense/schemas/search_result_hit.ex index d054c17..2f4e55d 100644 --- a/lib/open_api_typesense/schemas/search_result_hit.ex +++ b/lib/open_api_typesense/schemas/search_result_hit.ex @@ -9,6 +9,8 @@ defmodule OpenApiTypesense.SearchResultHit do geo_distance_meters: OpenApiTypesense.SearchResultHitGeoDistanceMeters.t(), highlight: map, highlights: [OpenApiTypesense.SearchHighlight.t()], + hybrid_search_info: OpenApiTypesense.SearchResultHitHybridSearchInfo.t(), + search_index: integer, text_match: integer, text_match_info: OpenApiTypesense.SearchResultHitTextMatchInfo.t(), vector_distance: number @@ -19,6 +21,8 @@ defmodule OpenApiTypesense.SearchResultHit do :geo_distance_meters, :highlight, :highlights, + :hybrid_search_info, + :search_index, :text_match, :text_match_info, :vector_distance @@ -70,6 +74,8 @@ defmodule OpenApiTypesense.SearchResultHit do geo_distance_meters: {OpenApiTypesense.SearchResultHitGeoDistanceMeters, :t}, highlight: :map, highlights: [{OpenApiTypesense.SearchHighlight, :t}], + hybrid_search_info: {OpenApiTypesense.SearchResultHitHybridSearchInfo, :t}, + search_index: :integer, text_match: :integer, text_match_info: {OpenApiTypesense.SearchResultHitTextMatchInfo, :t}, vector_distance: :number diff --git a/lib/open_api_typesense/schemas/search_result_hit_hybrid_search_info.ex b/lib/open_api_typesense/schemas/search_result_hit_hybrid_search_info.ex new file mode 100644 index 0000000..09a07ed --- /dev/null +++ b/lib/open_api_typesense/schemas/search_result_hit_hybrid_search_info.ex @@ -0,0 +1,18 @@ +defmodule OpenApiTypesense.SearchResultHitHybridSearchInfo do + @moduledoc """ + Provides struct and type for a SearchResultHitHybridSearchInfo + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{rank_fusion_score: number} + + defstruct [:rank_fusion_score] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [rank_fusion_score: :number] + end +end diff --git a/mix.exs b/mix.exs index 28d3665..dee8701 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule OpenApiTypesense.MixProject do @source_url "https://github.com/jaeyson/open_api_typesense" @hex_url "https://hexdocs.pm/open_api_typesense" - @version "1.0.4" + @version "1.1.0" def project do [ diff --git a/priv/open_api.yml b/priv/open_api.yml index df900b7..57942ab 100644 --- a/priv/open_api.yml +++ b/priv/open_api.yml @@ -3,8 +3,24 @@ info: title: Typesense API description: "An open source search engine for building delightful search experiences." version: '28.0' + license: + name: GPL-3.0 + url: https://opensource.org/licenses/GPL-3.0 +servers: + - url: "{protocol}://{hostname}:{port}" + description: Typesense Server + variables: + protocol: + default: http + description: The protocol of your Typesense server + hostname: + default: localhost + description: The hostname of your Typesense server + port: + default: "8108" + description: The port of your Typesense server externalDocs: - description: Find out more about Typesense + description: Find out more about Typsesense url: https://typesense.org security: - api_key_header: [] @@ -66,6 +82,12 @@ tags: externalDocs: description: Find out more url: https://typesense.org/docs/28.0/api/stemming.html + - name: nl_search_models + description: Manage NL search models + externalDocs: + description: Find out more + url: https://typesense.org/docs/29.0/api/natural-language-search.html + paths: /collections: get: @@ -1229,6 +1251,7 @@ paths: tags: - conversations post: + summary: Create a conversation model description: Create a Conversation Model operationId: createConversationModel requestBody: @@ -2501,6 +2524,171 @@ paths: application/json: schema: $ref: "#/components/schemas/ApiResponse" + /nl_search_models: + get: + tags: + - nl_search_models + summary: List all NL search models + description: Retrieve all NL search models. + operationId: retrieveAllNLSearchModels + responses: + '200': + description: List of all NL search models + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NLSearchModelSchema' + '401': + description: "Missing API key" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + post: + tags: + - nl_search_models + summary: Create a NL search model + description: Create a new NL search model. + operationId: createNLSearchModel + requestBody: + description: The NL search model to be created + content: + application/json: + schema: + $ref: '#/components/schemas/NLSearchModelCreateSchema' + required: true + responses: + '201': + description: NL search model successfully created + content: + application/json: + schema: + $ref: '#/components/schemas/NLSearchModelSchema' + '401': + description: "Missing API key" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + '400': + description: Bad request, see error message for details + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + /nl_search_models/{modelId}: + get: + tags: + - nl_search_models + summary: Retrieve a NL search model + description: Retrieve a specific NL search model by its ID. + operationId: retrieveNLSearchModel + parameters: + - name: modelId + in: path + description: The ID of the NL search model to retrieve + required: true + schema: + type: string + responses: + '200': + description: NL search model fetched + content: + application/json: + schema: + $ref: '#/components/schemas/NLSearchModelSchema' + '401': + description: "Missing API key" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + '404': + description: NL search model not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + put: + tags: + - nl_search_models + summary: Update a NL search model + description: Update an existing NL search model. + operationId: updateNLSearchModel + parameters: + - name: modelId + in: path + description: The ID of the NL search model to update + required: true + schema: + type: string + requestBody: + description: The NL search model fields to update + content: + application/json: + schema: + $ref: '#/components/schemas/NLSearchModelUpdateSchema' + required: true + responses: + '200': + description: NL search model successfully updated + content: + application/json: + schema: + $ref: '#/components/schemas/NLSearchModelSchema' + '401': + description: "Missing API key" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + '400': + description: Bad request, see error message for details + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + '404': + description: NL search model not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + delete: + tags: + - nl_search_models + summary: Delete a NL search model + description: Delete a specific NL search model by its ID. + operationId: deleteNLSearchModel + parameters: + - name: modelId + in: path + description: The ID of the NL search model to delete + required: true + schema: + type: string + responses: + '200': + description: NL search model successfully deleted + content: + application/json: + schema: + $ref: '#/components/schemas/NLSearchModelDeleteSchema' + '401': + description: "Missing API key" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + '404': + description: NL search model not found + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + components: schemas: CollectionSchema: @@ -2568,6 +2756,10 @@ components: default: [] voice_query_model: $ref: "#/components/schemas/VoiceQueryModelCollectionConfig" + metadata: + type: object + description: > + Optional details about the collection, e.g., when it was created, who created it etc. CollectionUpdateSchema: required: - fields @@ -2588,6 +2780,10 @@ components: facet: true items: $ref: "#/components/schemas/Field" + metadata: + type: object + description: > + Optional details about the collection, e.g., when it was created, who created it etc. CollectionResponse: allOf: - $ref: "#/components/schemas/CollectionSchema" @@ -2811,25 +3007,32 @@ components: items: $ref: "#/components/schemas/SearchResultHit" request_params: + $ref: "#/components/schemas/SearchRequestParams" + conversation: + $ref: "#/components/schemas/SearchResultConversation" + union_request_params: + type: array + description: Returned only for union query response. + items: + $ref: "#/components/schemas/SearchRequestParams" + SearchRequestParams: + type: object + required: + - collection_name + - q + - per_page + properties: + collection_name: + type: string + q: + type: string + per_page: + type: integer + voice_query: type: object - required: - - collection_name - - q - - per_page properties: - collection_name: - type: string - q: + transcribed_query: type: string - per_page: - type: integer - voice_query: - type: object - properties: - transcribed_query: - type: string - conversation: - $ref: "#/components/schemas/SearchResultConversation" SearchResultConversation: type: object required: @@ -2912,6 +3115,17 @@ components: type: number format: float description: Distance between the query vector and matching document's vector value + hybrid_search_info: + type: object + description: Information about hybrid search scoring + properties: + rank_fusion_score: + type: number + format: float + description: Combined score from rank fusion of text and vector search + search_index: + type: integer + description: Returned only for union query response. Indicates the index of the query which this document matched to. example: highlights: company_name: @@ -3286,6 +3500,14 @@ components: against. Multiple fields are separated with a comma. type: string + nl_query: + description: Whether to use natural language processing to parse the query. + type: boolean + + nl_model_id: + description: The ID of the natural language model to use. + type: string + query_by_weights: description: The relative weight to give each `query_by` field when ranking results. @@ -4049,6 +4271,7 @@ components: properties: union: type: boolean + default: false description: When true, merges the search results from each search query into a single ordered set of hits. searches: type: array @@ -4339,12 +4562,6 @@ components: properties: name: type: string - # client libraries already have .create, .upsert,... methods so we omit the `action` param - DocumentIndexParameters: - type: object - properties: - dirty_values: - $ref: "#/components/schemas/DirtyValues" DirtyValues: type: string enum: [coerce_or_reject, coerce_or_drop, drop, reject] @@ -4354,6 +4571,11 @@ components: DropTokensMode: type: string enum: [right_to_left, left_to_right, both_sides:3] + description: > + Dictates the direction in which the words in the query must be dropped when the original words in the query do not appear in any document. + Values: right_to_left (default), left_to_right, both_sides:3 + A note on both_sides:3 - for queries upto 3 tokens (words) in length, this mode will drop tokens from both sides and exhaustively rank all matching results. + If query length is greater than 3 words, Typesense will just fallback to default behavior of right_to_left ConversationModelCreateSchema: required: - model_name @@ -4445,8 +4667,100 @@ components: type: string description: The root form of the word example: person + NLSearchModelBase: + type: object + properties: + model_name: + type: string + description: Name of the NL model to use + api_key: + type: string + description: API key for the NL model service + api_url: + type: string + description: Custom API URL for the NL model service + max_bytes: + type: integer + description: Maximum number of bytes to process + temperature: + type: number + description: Temperature parameter for the NL model + system_prompt: + type: string + description: System prompt for the NL model + top_p: + type: number + description: Top-p parameter for the NL model (Google-specific) + top_k: + type: integer + description: Top-k parameter for the NL model (Google-specific) + stop_sequences: + type: array + items: + type: string + description: Stop sequences for the NL model (Google-specific) + api_version: + type: string + description: API version for the NL model service + project_id: + type: string + description: Project ID for GCP Vertex AI + access_token: + type: string + description: Access token for GCP Vertex AI + refresh_token: + type: string + description: Refresh token for GCP Vertex AI + client_id: + type: string + description: Client ID for GCP Vertex AI + client_secret: + type: string + description: Client secret for GCP Vertex AI + region: + type: string + description: Region for GCP Vertex AI + max_output_tokens: + type: integer + description: Maximum output tokens for GCP Vertex AI + account_id: + type: string + description: Account ID for Cloudflare-specific models + + NLSearchModelCreateSchema: + allOf: + - $ref: '#/components/schemas/NLSearchModelBase' + - type: object + properties: + id: + type: string + description: Optional ID for the NL search model + + NLSearchModelSchema: + allOf: + - $ref: '#/components/schemas/NLSearchModelCreateSchema' + - type: object + required: + - id + properties: + id: + type: string + description: ID of the NL search model + + NLSearchModelUpdateSchema: + $ref: '#/components/schemas/NLSearchModelCreateSchema' + + NLSearchModelDeleteSchema: + type: object + required: + - id + properties: + id: + type: string + description: ID of the deleted NL search model + securitySchemes: api_key_header: type: apiKey name: X-TYPESENSE-API-KEY - in: header + in: header \ No newline at end of file diff --git a/test/operations/nl_search_models_test.exs b/test/operations/nl_search_models_test.exs new file mode 100644 index 0000000..0e08efd --- /dev/null +++ b/test/operations/nl_search_models_test.exs @@ -0,0 +1,95 @@ +defmodule NlSearchModelsTest do + use ExUnit.Case, async: true + + alias OpenApiTypesense.ApiKey + alias OpenApiTypesense.ApiKeysResponse + alias OpenApiTypesense.ApiKeyDeleteResponse + alias OpenApiTypesense.Connection + alias OpenApiTypesense.NlSearchModels + + setup_all do + conn = Connection.new() + map_conn = %{api_key: "xyz", host: "localhost", port: 8108, scheme: "http"} + + api_key_schema = %{ + actions: ["documents:search"], + collections: ["companies"], + description: "Search-only companies key" + } + + on_exit(fn -> + {:ok, %ApiKeysResponse{keys: keys}} = Keys.get_keys() + + keys + |> Enum.each(fn key -> + {:ok, %ApiKeyDeleteResponse{}} = Keys.delete_key(key.id) + end) + end) + + %{api_key_schema: api_key_schema, conn: conn, map_conn: map_conn} + end + + @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] + test "success: get a specific key", %{ + api_key_schema: api_key_schema, + conn: conn, + map_conn: map_conn + } do + assert {:ok, api_key} = Keys.create_key(api_key_schema) + + key_id = api_key.id + + assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id) + assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, []) + assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, conn: conn) + assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, conn: map_conn) + end + + @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] + test "success: list API keys", %{conn: conn, map_conn: map_conn} do + assert {:ok, %ApiKeysResponse{keys: keys}} = Keys.get_keys() + assert length(keys) >= 0 + + assert {:ok, %ApiKeysResponse{}} = Keys.get_keys([]) + assert {:ok, %ApiKeysResponse{}} = Keys.get_keys(conn: conn) + assert {:ok, %ApiKeysResponse{}} = Keys.get_keys(conn: map_conn) + end + + @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] + test "success: delete an API key", %{ + api_key_schema: api_key_schema, + conn: conn, + map_conn: map_conn + } do + assert {:ok, api_key} = Keys.create_key(api_key_schema) + + key_id = api_key.id + + assert {:ok, %ApiKeyDeleteResponse{id: ^key_id}} = Keys.delete_key(key_id) + assert {:error, _} = Keys.delete_key(key_id, []) + assert {:error, _} = Keys.delete_key(key_id, conn: conn) + assert {:error, _} = Keys.delete_key(key_id, conn: map_conn) + end + + @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] + test "success: create an search-only API key", %{ + api_key_schema: api_key_schema, + conn: conn, + map_conn: map_conn + } do + assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema) + assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, []) + assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, conn: conn) + assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, conn: map_conn) + end + + @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] + test "success: create an admin API key", %{api_key_schema: api_key_schema} do + body = + api_key_schema + |> Map.put(:actions, ["*"]) + |> Map.put(:collections, ["*"]) + + assert {:ok, %ApiKey{}} = Keys.create_key(body) + end +end diff --git a/test/operations/stemming_test.exs b/test/operations/stemming_test.exs index 20f9cf3..fbd2051 100644 --- a/test/operations/stemming_test.exs +++ b/test/operations/stemming_test.exs @@ -32,7 +32,7 @@ defmodule StemmingTest do %{id: id, conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": false, "27.0": false, "26.0": false] + @tag ["29.0": true, "28.0": true, "27.1": false, "27.0": false, "26.0": false] test "success: create stemming dictionaries", %{conn: conn, map_conn: map_conn} do id = "example-stemming" @@ -64,7 +64,7 @@ defmodule StemmingTest do ]} = Stemming.import_stemming_dictionary(body, id: id, conn: map_conn) end - @tag ["28.0": true, "27.1": false, "27.0": false, "26.0": false] + @tag ["29.0": true, "28.0": true, "27.1": false, "27.0": false, "26.0": false] test "success: list stemming dictionaries", %{conn: conn, map_conn: map_conn} do assert {:ok, %Stemming{}} = Stemming.list_stemming_dictionaries() assert {:ok, %Stemming{}} = Stemming.list_stemming_dictionaries([]) @@ -72,13 +72,13 @@ defmodule StemmingTest do assert {:ok, %Stemming{}} = Stemming.list_stemming_dictionaries(conn: map_conn) end - @tag ["28.0": true, "27.1": false, "27.0": false, "26.0": false] + @tag ["29.0": true, "28.0": true, "27.1": false, "27.0": false, "26.0": false] test "error: non-existent stemming dictionary" do - assert {:error, %ApiResponse{message: "Not Found"}} = + assert {:error, %ApiResponse{message: "Collection not found"}} = Stemming.get_stemming_dictionary("non-existent") end - @tag ["28.0": true, "27.1": false, "27.0": false, "26.0": false] + @tag ["29.0": true, "28.0": true, "27.1": false, "27.0": false, "26.0": false] test "success: get specific stemming dictionary", %{id: id, conn: conn, map_conn: map_conn} do body = [ %{"word" => "mice", "root" => "mouse"}, @@ -101,7 +101,7 @@ defmodule StemmingTest do Stemming.get_stemming_dictionary(id, conn: map_conn) end - @tag ["28.0": true, "27.1": false, "27.0": false, "26.0": false] + @tag ["29.0": true, "28.0": true, "27.1": false, "27.0": false, "26.0": false] test "field" do assert [dictionaries: [string: :generic]] = Stemming.__fields__(:list_stemming_dictionaries_200_json_resp) From 9413686df0137473e5b1b902ad02539eca1b50ad Mon Sep 17 00:00:00 2001 From: jaeyson Date: Wed, 1 Oct 2025 09:33:47 +0800 Subject: [PATCH 2/6] wip refactor --- .github/workflows/llm.yml | 91 +++ .gitignore | 2 + CHANGELOG.md | 18 + dev.env.example | 1 + .../operations/analytics.ex | 145 ++++- .../operations/nl_search_models.ex | 3 +- .../operations/operations.ex | 75 +-- .../schemas/analytics_event.ex | 26 + .../schemas/analytics_event_data.ex | 30 + .../schemas/analytics_events_response.ex | 54 ++ .../analytics_events_response_events.ex | 36 ++ .../schemas/analytics_rule.ex | 32 ++ .../schemas/analytics_rule_create.ex | 32 ++ .../schemas/analytics_rule_update.ex | 26 + .../schemas/analytics_rule_update_params.ex | 42 ++ .../schemas/analytics_status.ex | 42 ++ .../schemas/collection_response.ex | 3 + .../schemas/collection_schema.ex | 3 + .../schemas/collection_update_schema.ex | 10 +- .../schemas/drop_tokens_mode.ex | 18 + .../multi_search_collection_parameters.ex | 9 +- .../schemas/multi_search_result_item.ex | 3 + .../schemas/search_parameters.ex | 12 +- .../schemas/search_result.ex | 3 + .../voice_query_model_collection_config.ex | 2 +- priv/car_data.json | 222 ++++++++ priv/open_api.yml | 530 ++++++++++++------ test/operations/analytics_test.exs | 9 +- test/operations/collections_test.exs | 4 +- test/operations/nl_search_models_test.exs | 96 +--- test/operations/operations_test.exs | 22 +- test/operations/override_test.exs | 2 +- 32 files changed, 1266 insertions(+), 337 deletions(-) create mode 100644 .github/workflows/llm.yml create mode 100644 dev.env.example create mode 100644 lib/open_api_typesense/schemas/analytics_event.ex create mode 100644 lib/open_api_typesense/schemas/analytics_event_data.ex create mode 100644 lib/open_api_typesense/schemas/analytics_events_response.ex create mode 100644 lib/open_api_typesense/schemas/analytics_events_response_events.ex create mode 100644 lib/open_api_typesense/schemas/analytics_rule.ex create mode 100644 lib/open_api_typesense/schemas/analytics_rule_create.ex create mode 100644 lib/open_api_typesense/schemas/analytics_rule_update.ex create mode 100644 lib/open_api_typesense/schemas/analytics_rule_update_params.ex create mode 100644 lib/open_api_typesense/schemas/analytics_status.ex create mode 100644 lib/open_api_typesense/schemas/drop_tokens_mode.ex create mode 100644 priv/car_data.json diff --git a/.github/workflows/llm.yml b/.github/workflows/llm.yml new file mode 100644 index 0000000..b67276e --- /dev/null +++ b/.github/workflows/llm.yml @@ -0,0 +1,91 @@ +name: LLM +on: + push: + push: + branches: ["main"] + pull_request: + branches: ["main"] +jobs: + ci_workflow: + uses: ./.github/workflows/ci_v29.0.yml + secrets: inherit + + staging_deploy: + runs-on: ubuntu-latest + needs: [ci_workflow] + + env: + MIX_ENV: test + + strategy: + matrix: + include: + - typesense: '29.0' + otp: '28' + elixir: '1.18' + + services: + typesense: + image: typesense/typesense:${{ matrix.typesense }} + + steps: + - name: Start Typesense + run: | + docker run -id \ + -p 8108:8108 \ + --name typesense \ + -v /tmp/typesense-data:/data \ + -v /tmp/typesense-analytics-data:/analytics-data \ + typesense/typesense:${{ matrix.typesense}} \ + --api-key xyz \ + --data-dir /data \ + --enable-search-analytics=true \ + --analytics-dir=/analytics-data \ + --analytics-flush-interval=60 \ + --analytics-minute-rate-limit=100 \ + --enable-cors + + - name: Wait for Typesense to be healthy + shell: bash + run: | + start_time=$(date +%s) + timeout=30 + counter=0 + until curl -s http://localhost:8108/health | grep -q '"ok":true'; do + if [ $counter -eq $timeout ]; then + echo "Timed out waiting for Typesense to be healthy" + exit 1 + fi + echo "Waiting for Typesense to be healthy..." + sleep 1 + counter=$((counter + 1)) + done + end_time=$(date +%s) + elapsed=$((end_time - start_time)) + echo "Typesense healthcheck elapsed: ${elapsed}s" + + - name: Checkout repo + uses: actions/checkout@v5 + + - name: Setup Elixir/OTP + uses: erlef/setup-beam@v1 + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + + - name: Cache typesense-data + uses: actions/cache@v4 + with: + path: | + typesense-data + key: ${{ runner.os }}-typesense-data + restore-keys: | + ${{ runner.os }}-typesense-data + + - name: Test natural language search + run: mix test --only nls:true --trace + if: ${{ github.event_name == 'pull_request' && github.ref == 'refs/heads/main' }} + + - name: Post test coverage to Coveralls + run: mix coveralls.github + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} diff --git a/.gitignore b/.gitignore index c4eb9a7..f4d5269 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,7 @@ open_api_typesense-*.tar .elixir_ls **.DS_Store +*.env + /priv/plts/*.plt /priv/plts/*.plt.hash diff --git a/CHANGELOG.md b/CHANGELOG.md index ea501f1..75bf8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## major.minor.patch (yyyy.mm.dd) +## 1.1.1 ??? + +### Added + +* `NlSearchModels` operation +* `Analytics.flush_analytics/1` +* `Analytics.get_analytics_status/1` +* `Analytics.get_analytics_events/1` + +### Chore + +* update `priv/open_api.yml` to https://github.com/typesense/typesense-api-spec/commit/eac4e46bc934dd1f621406602c2c46184961788f +* Changed `DropTokensMode` schema from string to object. + +### Removed + +* `Operations.config/2`. Use `Operations.toggle_slow_request_log/2` + ## 1.0.4 (2025.06.18) ### Fixed diff --git a/dev.env.example b/dev.env.example new file mode 100644 index 0000000..833376a --- /dev/null +++ b/dev.env.example @@ -0,0 +1 @@ +export GEMINI_API_KEY=google_xyz \ No newline at end of file diff --git a/lib/open_api_typesense/operations/analytics.ex b/lib/open_api_typesense/operations/analytics.ex index 88efd0a..688ff54 100644 --- a/lib/open_api_typesense/operations/analytics.ex +++ b/lib/open_api_typesense/operations/analytics.ex @@ -10,7 +10,7 @@ defmodule OpenApiTypesense.Analytics do @doc """ Create an analytics event - Sending events for analytics e.g rank search results based on popularity. + Submit a single analytics event. The event must correspond to an existing analytics rule by name. """ @doc since: "0.4.0" @spec create_analytics_event( @@ -28,9 +28,9 @@ defmodule OpenApiTypesense.Analytics do url: "/analytics/events", body: body, method: :post, - request: [{"application/json", {OpenApiTypesense.AnalyticsEventCreateSchema, :t}}], + request: [{"application/json", {OpenApiTypesense.AnalyticsEvent, :t}}], response: [ - {201, {OpenApiTypesense.AnalyticsEventCreateResponse, :t}}, + {200, {OpenApiTypesense.AnalyticsEventCreateResponse, :t}}, {400, {OpenApiTypesense.ApiResponse, :t}}, {401, {OpenApiTypesense.ApiResponse, :t}} ], @@ -39,13 +39,17 @@ defmodule OpenApiTypesense.Analytics do end @doc """ - Creates an analytics rule + Create analytics rule(s) - When an analytics rule is created, we give it a name and describe the type, the source collections and the destination collection. + Create one or more analytics rules. You can send a single rule object or an array of rule objects. """ @doc since: "0.4.0" - @spec create_analytics_rule(body :: OpenApiTypesense.AnalyticsRuleSchema.t(), opts :: keyword) :: - {:ok, OpenApiTypesense.AnalyticsRuleSchema.t()} + @spec create_analytics_rule( + body :: + OpenApiTypesense.AnalyticsRuleCreate.t() | [OpenApiTypesense.AnalyticsRuleCreate.t()], + opts :: keyword + ) :: + {:ok, OpenApiTypesense.AnalyticsRule.t() | [map | OpenApiTypesense.AnalyticsRule.t()]} | {:error, OpenApiTypesense.ApiResponse.t()} def create_analytics_rule(body, opts \\ []) do client = opts[:client] || @default_client @@ -56,9 +60,21 @@ defmodule OpenApiTypesense.Analytics do url: "/analytics/rules", body: body, method: :post, - request: [{"application/json", {OpenApiTypesense.AnalyticsRuleSchema, :t}}], + request: [ + {"application/json", + {:union, + [ + {OpenApiTypesense.AnalyticsRuleCreate, :t}, + [{OpenApiTypesense.AnalyticsRuleCreate, :t}] + ]}} + ], response: [ - {201, {OpenApiTypesense.AnalyticsRuleSchema, :t}}, + {200, + {:union, + [ + {OpenApiTypesense.AnalyticsRule, :t}, + [union: [:map, {OpenApiTypesense.AnalyticsRule, :t}]] + ]}}, {400, {OpenApiTypesense.ApiResponse, :t}}, {401, {OpenApiTypesense.ApiResponse, :t}}, {404, {OpenApiTypesense.ApiResponse, :t}} @@ -74,8 +90,7 @@ defmodule OpenApiTypesense.Analytics do """ @doc since: "0.4.0" @spec delete_analytics_rule(rule_name :: String.t(), opts :: keyword) :: - {:ok, OpenApiTypesense.AnalyticsRuleDeleteResponse.t()} - | {:error, OpenApiTypesense.ApiResponse.t()} + {:ok, OpenApiTypesense.AnalyticsRule.t()} | {:error, OpenApiTypesense.ApiResponse.t()} def delete_analytics_rule(rule_name, opts \\ []) do client = opts[:client] || @default_client @@ -85,7 +100,7 @@ defmodule OpenApiTypesense.Analytics do url: "/analytics/rules/#{rule_name}", method: :delete, response: [ - {200, {OpenApiTypesense.AnalyticsRuleDeleteResponse, :t}}, + {200, {OpenApiTypesense.AnalyticsRule, :t}}, {401, {OpenApiTypesense.ApiResponse, :t}}, {404, {OpenApiTypesense.ApiResponse, :t}} ], @@ -93,6 +108,82 @@ defmodule OpenApiTypesense.Analytics do }) end + @doc """ + Flush in-memory analytics to disk + + Triggers a flush of analytics data to persistent storage. + """ + @doc since: "1.1.0" + @spec flush_analytics(opts :: keyword) :: + {:ok, OpenApiTypesense.AnalyticsEventCreateResponse.t()} | :error + def flush_analytics(opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [], + call: {OpenApiTypesense.Analytics, :flush_analytics}, + url: "/analytics/flush", + method: :post, + response: [{200, {OpenApiTypesense.AnalyticsEventCreateResponse, :t}}], + opts: opts + }) + end + + @doc """ + Retrieve analytics events + + Retrieve the most recent events for a user and rule. + + ## Options + + * `user_id` + * `name`: Analytics rule name + * `n`: Number of events to return (max 1000) + + """ + @doc since: "1.1.0" + @spec get_analytics_events(opts :: keyword) :: + {:ok, OpenApiTypesense.AnalyticsEventsResponse.t()} + | {:error, OpenApiTypesense.ApiResponse.t()} + def get_analytics_events(opts \\ []) do + client = opts[:client] || @default_client + query = Keyword.take(opts, [:n, :name, :user_id]) + + client.request(%{ + args: [], + call: {OpenApiTypesense.Analytics, :get_analytics_events}, + url: "/analytics/events", + method: :get, + query: query, + response: [ + {200, {OpenApiTypesense.AnalyticsEventsResponse, :t}}, + {400, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end + + @doc """ + Get analytics subsystem status + + Returns sizes of internal analytics buffers and queues. + """ + @doc since: "1.1.0" + @spec get_analytics_status(opts :: keyword) :: + {:ok, OpenApiTypesense.AnalyticsStatus.t()} | :error + def get_analytics_status(opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [], + call: {OpenApiTypesense.Analytics, :get_analytics_status}, + url: "/analytics/status", + method: :get, + response: [{200, {OpenApiTypesense.AnalyticsStatus, :t}}], + opts: opts + }) + end + @doc """ Retrieves an analytics rule @@ -100,8 +191,7 @@ defmodule OpenApiTypesense.Analytics do """ @doc since: "0.4.0" @spec retrieve_analytics_rule(rule_name :: String.t(), opts :: keyword) :: - {:ok, OpenApiTypesense.AnalyticsRuleSchema.t()} - | {:error, OpenApiTypesense.ApiResponse.t()} + {:ok, OpenApiTypesense.AnalyticsRule.t()} | {:error, OpenApiTypesense.ApiResponse.t()} def retrieve_analytics_rule(rule_name, opts \\ []) do client = opts[:client] || @default_client @@ -111,7 +201,7 @@ defmodule OpenApiTypesense.Analytics do url: "/analytics/rules/#{rule_name}", method: :get, response: [ - {200, {OpenApiTypesense.AnalyticsRuleSchema, :t}}, + {200, {OpenApiTypesense.AnalyticsRule, :t}}, {401, {OpenApiTypesense.ApiResponse, :t}}, {404, {OpenApiTypesense.ApiResponse, :t}} ], @@ -120,24 +210,30 @@ defmodule OpenApiTypesense.Analytics do end @doc """ - Retrieves all analytics rules + Retrieve analytics rules + + Retrieve all analytics rules. Use the optional rule_tag filter to narrow down results. + + ## Options + + * `rule_tag`: Filter rules by rule_tag - Retrieve the details of all analytics rules """ @doc since: "0.4.0" @spec retrieve_analytics_rules(opts :: keyword) :: - {:ok, OpenApiTypesense.AnalyticsRulesRetrieveSchema.t()} - | {:error, OpenApiTypesense.ApiResponse.t()} + {:ok, [OpenApiTypesense.AnalyticsRule.t()]} | {:error, OpenApiTypesense.ApiResponse.t()} def retrieve_analytics_rules(opts \\ []) do client = opts[:client] || @default_client + query = Keyword.take(opts, [:rule_tag]) client.request(%{ args: [], call: {OpenApiTypesense.Analytics, :retrieve_analytics_rules}, url: "/analytics/rules", method: :get, + query: query, response: [ - {200, {OpenApiTypesense.AnalyticsRulesRetrieveSchema, :t}}, + {200, [{OpenApiTypesense.AnalyticsRule, :t}]}, {401, {OpenApiTypesense.ApiResponse, :t}} ], opts: opts @@ -152,11 +248,10 @@ defmodule OpenApiTypesense.Analytics do @doc since: "0.4.0" @spec upsert_analytics_rule( rule_name :: String.t(), - body :: OpenApiTypesense.AnalyticsRuleUpsertSchema.t(), + body :: OpenApiTypesense.AnalyticsRuleUpdate.t(), opts :: keyword ) :: - {:ok, OpenApiTypesense.AnalyticsRuleSchema.t()} - | {:error, OpenApiTypesense.ApiResponse.t()} + {:ok, OpenApiTypesense.AnalyticsRule.t()} | {:error, OpenApiTypesense.ApiResponse.t()} def upsert_analytics_rule(rule_name, body, opts \\ []) do client = opts[:client] || @default_client @@ -166,9 +261,9 @@ defmodule OpenApiTypesense.Analytics do url: "/analytics/rules/#{rule_name}", body: body, method: :put, - request: [{"application/json", {OpenApiTypesense.AnalyticsRuleUpsertSchema, :t}}], + request: [{"application/json", {OpenApiTypesense.AnalyticsRuleUpdate, :t}}], response: [ - {200, {OpenApiTypesense.AnalyticsRuleSchema, :t}}, + {200, {OpenApiTypesense.AnalyticsRule, :t}}, {400, {OpenApiTypesense.ApiResponse, :t}}, {401, {OpenApiTypesense.ApiResponse, :t}} ], diff --git a/lib/open_api_typesense/operations/nl_search_models.ex b/lib/open_api_typesense/operations/nl_search_models.ex index fb56e37..98cbc96 100644 --- a/lib/open_api_typesense/operations/nl_search_models.ex +++ b/lib/open_api_typesense/operations/nl_search_models.ex @@ -32,7 +32,8 @@ defmodule OpenApiTypesense.NlSearchModels do response: [ {201, {OpenApiTypesense.NLSearchModelSchema, :t}}, {400, {OpenApiTypesense.ApiResponse, :t}}, - {401, {OpenApiTypesense.ApiResponse, :t}} + {401, {OpenApiTypesense.ApiResponse, :t}}, + {409, {OpenApiTypesense.ApiResponse, :t}} ], opts: opts }) diff --git a/lib/open_api_typesense/operations/operations.ex b/lib/open_api_typesense/operations/operations.ex index 1f97cb2..c856b22 100644 --- a/lib/open_api_typesense/operations/operations.ex +++ b/lib/open_api_typesense/operations/operations.ex @@ -8,9 +8,9 @@ defmodule OpenApiTypesense.Operations do @default_client OpenApiTypesense.Client @doc """ - Clears the cache + Clear the cached responses of search requests in the LRU cache. - Responses of search requests that are sent with use_cache parameter are cached in a LRU cache. Clears cache completely. + Clear the cached responses of search requests that are sent with `use_cache` parameter in the LRU cache. """ @doc since: "0.4.2" @spec clear_cache(opts :: keyword) :: @@ -32,19 +32,19 @@ defmodule OpenApiTypesense.Operations do end @doc """ - Compaction of the underlying RocksDB database + Compacting the on-disk database Typesense uses RocksDB to store your documents on the disk. If you do frequent writes or updates, you could benefit from running a compaction of the underlying RocksDB database. This could reduce the size of the database and decrease read latency. While the database will not block during this operation, we recommend running it during off-peak hours. """ @doc since: "0.4.2" - @spec compact(opts :: keyword) :: + @spec compact_db(opts :: keyword) :: {:ok, OpenApiTypesense.SuccessStatus.t()} | {:error, OpenApiTypesense.ApiResponse.t()} - def compact(opts \\ []) do + def compact_db(opts \\ []) do client = opts[:client] || @default_client client.request(%{ args: [], - call: {OpenApiTypesense.Operations, :compact}, + call: {OpenApiTypesense.Operations, :compact_db}, url: "/operations/db/compact", method: :post, response: [ @@ -55,33 +55,6 @@ defmodule OpenApiTypesense.Operations do }) end - @doc """ - Enable logging of requests that take over a defined threshold of time. - - Slow requests are logged to the primary log file, with the prefix SLOW REQUEST. Default is -1 which disables slow request logging. - """ - @doc since: "0.4.2" - @spec config(body :: OpenApiTypesense.ConfigSchema.t(), opts :: keyword) :: - {:ok, OpenApiTypesense.SuccessStatus.t()} | {:error, OpenApiTypesense.ApiResponse.t()} - def config body, opts \\ [] do - client = opts[:client] || @default_client - - client.request(%{ - args: [body: body], - call: {OpenApiTypesense.Operations, :config}, - url: "/config", - body: body, - method: :post, - request: [{"application/json", {OpenApiTypesense.ConfigSchema, :t}}], - response: [ - {201, {OpenApiTypesense.SuccessStatus, :t}}, - {400, {OpenApiTypesense.ApiResponse, :t}}, - {401, {OpenApiTypesense.ApiResponse, :t}} - ], - opts: opts - }) - end - @doc """ Get the status of in-progress schema change operations @@ -186,6 +159,42 @@ defmodule OpenApiTypesense.Operations do }) end + @doc """ + Toggle Slow Request Log + + Enable logging of requests that take over a defined threshold of time. Default is `-1` which disables slow request logging. Slow requests are logged to the primary log file, with the prefix SLOW REQUEST. + + ## Required body + + * `log-slow-requests-time-ms`: Defaults to `-1` + + + ## Example + iex> body = %{"log-slow-requests-time-ms" => 2_000} + iex> OpenApiTypesense.Operations.toggle_slow_request_log(body) + """ + @doc since: "1.1.0" + @spec toggle_slow_request_log(body :: map, opts :: keyword) :: + {:ok, OpenApiTypesense.SuccessStatus.t()} | {:error, OpenApiTypesense.ApiResponse.t()} + def toggle_slow_request_log(body, opts \\ []) do + client = opts[:client] || @default_client + + client.request(%{ + args: [body: body], + call: {OpenApiTypesense.Operations, :toggle_slow_request_log}, + url: "/config", + body: body, + method: :post, + request: [{"application/json", :map}], + response: [ + {201, {OpenApiTypesense.SuccessStatus, :t}}, + {400, {OpenApiTypesense.ApiResponse, :t}}, + {401, {OpenApiTypesense.ApiResponse, :t}} + ], + opts: opts + }) + end + @doc """ Triggers a follower node to initiate the raft voting process, which triggers leader re-election. diff --git a/lib/open_api_typesense/schemas/analytics_event.ex b/lib/open_api_typesense/schemas/analytics_event.ex new file mode 100644 index 0000000..5ff570a --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_event.ex @@ -0,0 +1,26 @@ +defmodule OpenApiTypesense.AnalyticsEvent do + @moduledoc """ + Provides struct and type for a AnalyticsEvent + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + data: OpenApiTypesense.AnalyticsEventData.t(), + event_type: String.t(), + name: String.t() + } + + defstruct [:data, :event_type, :name] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + data: {OpenApiTypesense.AnalyticsEventData, :t}, + event_type: {:string, :generic}, + name: {:string, :generic} + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_event_data.ex b/lib/open_api_typesense/schemas/analytics_event_data.ex new file mode 100644 index 0000000..5d93959 --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_event_data.ex @@ -0,0 +1,30 @@ +defmodule OpenApiTypesense.AnalyticsEventData do + @moduledoc """ + Provides struct and type for a AnalyticsEventData + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + analytics_tag: String.t(), + doc_id: String.t(), + doc_ids: [String.t()], + q: String.t(), + user_id: String.t() + } + + defstruct [:analytics_tag, :doc_id, :doc_ids, :q, :user_id] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + analytics_tag: {:string, :generic}, + doc_id: {:string, :generic}, + doc_ids: [string: :generic], + q: {:string, :generic}, + user_id: {:string, :generic} + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_events_response.ex b/lib/open_api_typesense/schemas/analytics_events_response.ex new file mode 100644 index 0000000..a39a442 --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_events_response.ex @@ -0,0 +1,54 @@ +defmodule OpenApiTypesense.AnalyticsEventsResponse do + @moduledoc """ + Provides struct and type for a AnalyticsEventsResponse + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{events: [OpenApiTypesense.AnalyticsEventsResponseEvents.t()]} + + defstruct [:events] + + defimpl(Poison.Decoder, for: OpenApiTypesense.AnalyticsEventsResponse) do + def decode(value, %{as: struct}) do + mod = + case struct do + [m] -> m + m -> m + end + + filtered_type = + mod.__struct__.__fields__() + |> Enum.filter(fn {_field, v} -> + case v do + [{mod, :t}] when is_atom(mod) -> true + _ -> false + end + end) + + case filtered_type do + [{_key, [{module, :t}]} | _rest] = list when is_list(list) and is_atom(module) -> + Enum.reduce(list, value, fn {key, [{mod, :t}]}, acc -> + Map.update!(acc, key, fn data -> + body = OpenApiTypesense.Converter.to_atom_keys(data || [], safe: false) + + case body do + [] -> [] + _ -> Enum.map(body, &struct(mod, &1)) + end + end) + end) + + [] -> + value + end + end + end + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [events: [{OpenApiTypesense.AnalyticsEventsResponseEvents, :t}]] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_events_response_events.ex b/lib/open_api_typesense/schemas/analytics_events_response_events.ex new file mode 100644 index 0000000..c1c29cb --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_events_response_events.ex @@ -0,0 +1,36 @@ +defmodule OpenApiTypesense.AnalyticsEventsResponseEvents do + @moduledoc """ + Provides struct and type for a AnalyticsEventsResponseEvents + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + collection: String.t(), + doc_id: String.t(), + doc_ids: [String.t()], + event_type: String.t(), + name: String.t(), + query: String.t(), + timestamp: integer, + user_id: String.t() + } + + defstruct [:collection, :doc_id, :doc_ids, :event_type, :name, :query, :timestamp, :user_id] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + collection: {:string, :generic}, + doc_id: {:string, :generic}, + doc_ids: [string: :generic], + event_type: {:string, :generic}, + name: {:string, :generic}, + query: {:string, :generic}, + timestamp: :integer, + user_id: {:string, :generic} + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_rule.ex b/lib/open_api_typesense/schemas/analytics_rule.ex new file mode 100644 index 0000000..293f7c2 --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_rule.ex @@ -0,0 +1,32 @@ +defmodule OpenApiTypesense.AnalyticsRule do + @moduledoc """ + Provides struct and type for a AnalyticsRule + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + collection: String.t(), + event_type: String.t(), + name: String.t(), + params: map, + rule_tag: String.t(), + type: String.t() + } + + defstruct [:collection, :event_type, :name, :params, :rule_tag, :type] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + collection: {:string, :generic}, + event_type: {:string, :generic}, + name: {:string, :generic}, + params: :map, + rule_tag: {:string, :generic}, + type: {:enum, ["popular_queries", "nohits_queries", "counter", "log"]} + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_rule_create.ex b/lib/open_api_typesense/schemas/analytics_rule_create.ex new file mode 100644 index 0000000..a174792 --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_rule_create.ex @@ -0,0 +1,32 @@ +defmodule OpenApiTypesense.AnalyticsRuleCreate do + @moduledoc """ + Provides struct and type for a AnalyticsRuleCreate + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + collection: String.t(), + event_type: String.t(), + name: String.t(), + params: map, + rule_tag: String.t(), + type: String.t() + } + + defstruct [:collection, :event_type, :name, :params, :rule_tag, :type] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + collection: {:string, :generic}, + event_type: {:string, :generic}, + name: {:string, :generic}, + params: :map, + rule_tag: {:string, :generic}, + type: {:enum, ["popular_queries", "nohits_queries", "counter", "log"]} + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_rule_update.ex b/lib/open_api_typesense/schemas/analytics_rule_update.ex new file mode 100644 index 0000000..2dd0e41 --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_rule_update.ex @@ -0,0 +1,26 @@ +defmodule OpenApiTypesense.AnalyticsRuleUpdate do + @moduledoc """ + Provides struct and type for a AnalyticsRuleUpdate + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + name: String.t(), + params: OpenApiTypesense.AnalyticsRuleUpdateParams.t(), + rule_tag: String.t() + } + + defstruct [:name, :params, :rule_tag] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + name: {:string, :generic}, + params: {OpenApiTypesense.AnalyticsRuleUpdateParams, :t}, + rule_tag: {:string, :generic} + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_rule_update_params.ex b/lib/open_api_typesense/schemas/analytics_rule_update_params.ex new file mode 100644 index 0000000..034e324 --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_rule_update_params.ex @@ -0,0 +1,42 @@ +defmodule OpenApiTypesense.AnalyticsRuleUpdateParams do + @moduledoc """ + Provides struct and type for a AnalyticsRuleUpdateParams + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + capture_search_requests: boolean, + counter_field: String.t(), + destination_collection: String.t(), + expand_query: boolean, + limit: integer, + meta_fields: [String.t()], + weight: integer + } + + defstruct [ + :capture_search_requests, + :counter_field, + :destination_collection, + :expand_query, + :limit, + :meta_fields, + :weight + ] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + capture_search_requests: :boolean, + counter_field: {:string, :generic}, + destination_collection: {:string, :generic}, + expand_query: :boolean, + limit: :integer, + meta_fields: [string: :generic], + weight: :integer + ] + end +end diff --git a/lib/open_api_typesense/schemas/analytics_status.ex b/lib/open_api_typesense/schemas/analytics_status.ex new file mode 100644 index 0000000..12a0f6b --- /dev/null +++ b/lib/open_api_typesense/schemas/analytics_status.ex @@ -0,0 +1,42 @@ +defmodule OpenApiTypesense.AnalyticsStatus do + @moduledoc """ + Provides struct and type for a AnalyticsStatus + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{ + doc_counter_events: integer, + doc_log_events: integer, + log_prefix_queries: integer, + nohits_prefix_queries: integer, + popular_prefix_queries: integer, + query_counter_events: integer, + query_log_events: integer + } + + defstruct [ + :doc_counter_events, + :doc_log_events, + :log_prefix_queries, + :nohits_prefix_queries, + :popular_prefix_queries, + :query_counter_events, + :query_log_events + ] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [ + doc_counter_events: :integer, + doc_log_events: :integer, + log_prefix_queries: :integer, + nohits_prefix_queries: :integer, + popular_prefix_queries: :integer, + query_counter_events: :integer, + query_log_events: :integer + ] + end +end diff --git a/lib/open_api_typesense/schemas/collection_response.ex b/lib/open_api_typesense/schemas/collection_response.ex index c3ecd81..e7f1592 100644 --- a/lib/open_api_typesense/schemas/collection_response.ex +++ b/lib/open_api_typesense/schemas/collection_response.ex @@ -13,6 +13,7 @@ defmodule OpenApiTypesense.CollectionResponse do name: String.t(), num_documents: integer, symbols_to_index: [String.t()], + synonym_sets: [String.t()], token_separators: [String.t()], voice_query_model: OpenApiTypesense.VoiceQueryModelCollectionConfig.t() } @@ -23,6 +24,7 @@ defmodule OpenApiTypesense.CollectionResponse do :metadata, :name, :num_documents, + :synonym_sets, default_sorting_field: "", enable_nested_fields: false, symbols_to_index: [], @@ -80,6 +82,7 @@ defmodule OpenApiTypesense.CollectionResponse do name: {:string, :generic}, num_documents: :integer, symbols_to_index: [string: :generic], + synonym_sets: [string: :generic], token_separators: [string: :generic], voice_query_model: {OpenApiTypesense.VoiceQueryModelCollectionConfig, :t} ] diff --git a/lib/open_api_typesense/schemas/collection_schema.ex b/lib/open_api_typesense/schemas/collection_schema.ex index 2a6d84f..0e3f9fa 100644 --- a/lib/open_api_typesense/schemas/collection_schema.ex +++ b/lib/open_api_typesense/schemas/collection_schema.ex @@ -11,6 +11,7 @@ defmodule OpenApiTypesense.CollectionSchema do metadata: map, name: String.t(), symbols_to_index: [String.t()], + synonym_sets: [String.t()], token_separators: [String.t()], voice_query_model: OpenApiTypesense.VoiceQueryModelCollectionConfig.t() } @@ -19,6 +20,7 @@ defmodule OpenApiTypesense.CollectionSchema do :fields, :metadata, :name, + :synonym_sets, default_sorting_field: "", enable_nested_fields: false, symbols_to_index: [], @@ -74,6 +76,7 @@ defmodule OpenApiTypesense.CollectionSchema do metadata: :map, name: {:string, :generic}, symbols_to_index: [string: :generic], + synonym_sets: [string: :generic], token_separators: [string: :generic], voice_query_model: {OpenApiTypesense.VoiceQueryModelCollectionConfig, :t} ] diff --git a/lib/open_api_typesense/schemas/collection_update_schema.ex b/lib/open_api_typesense/schemas/collection_update_schema.ex index 418f1c5..dd72b08 100644 --- a/lib/open_api_typesense/schemas/collection_update_schema.ex +++ b/lib/open_api_typesense/schemas/collection_update_schema.ex @@ -4,9 +4,13 @@ defmodule OpenApiTypesense.CollectionUpdateSchema do """ use OpenApiTypesense.Encoder - @type t :: %__MODULE__{fields: [OpenApiTypesense.Field.t()], metadata: map} + @type t :: %__MODULE__{ + fields: [OpenApiTypesense.Field.t()], + metadata: map, + synonym_sets: [String.t()] + } - defstruct [:fields, :metadata] + defstruct [:fields, :metadata, :synonym_sets] defimpl(Poison.Decoder, for: OpenApiTypesense.CollectionUpdateSchema) do def decode(value, %{as: struct}) do @@ -49,6 +53,6 @@ defmodule OpenApiTypesense.CollectionUpdateSchema do def __fields__(type \\ :t) def __fields__(:t) do - [fields: [{OpenApiTypesense.Field, :t}], metadata: :map] + [fields: [{OpenApiTypesense.Field, :t}], metadata: :map, synonym_sets: [string: :generic]] end end diff --git a/lib/open_api_typesense/schemas/drop_tokens_mode.ex b/lib/open_api_typesense/schemas/drop_tokens_mode.ex new file mode 100644 index 0000000..ab63c69 --- /dev/null +++ b/lib/open_api_typesense/schemas/drop_tokens_mode.ex @@ -0,0 +1,18 @@ +defmodule OpenApiTypesense.DropTokensMode do + @moduledoc """ + Provides struct and type for a DropTokensMode + """ + use OpenApiTypesense.Encoder + + @type t :: %__MODULE__{match: String.t()} + + defstruct [:match] + + @doc false + @spec __fields__(atom) :: keyword + def __fields__(type \\ :t) + + def __fields__(:t) do + [match: {:enum, ["right_to_left", "left_to_right", "both_sides:3"]}] + end +end diff --git a/lib/open_api_typesense/schemas/multi_search_collection_parameters.ex b/lib/open_api_typesense/schemas/multi_search_collection_parameters.ex index aec0760..a263376 100644 --- a/lib/open_api_typesense/schemas/multi_search_collection_parameters.ex +++ b/lib/open_api_typesense/schemas/multi_search_collection_parameters.ex @@ -10,8 +10,9 @@ defmodule OpenApiTypesense.MultiSearchCollectionParameters do conversation: boolean, conversation_id: String.t(), conversation_model_id: String.t(), - drop_tokens_mode: String.t(), + drop_tokens_mode: OpenApiTypesense.DropTokensMode.t(), drop_tokens_threshold: integer, + enable_analytics: boolean, enable_overrides: boolean, enable_synonyms: boolean, enable_typos_for_alpha_numerical_tokens: boolean, @@ -132,7 +133,8 @@ defmodule OpenApiTypesense.MultiSearchCollectionParameters do :vector_query, :voice_query, :"x-typesense-api-key", - drop_tokens_mode: "right_to_left", + drop_tokens_mode: %OpenApiTypesense.DropTokensMode{}, + enable_analytics: true, enable_overrides: false, enable_typos_for_numerical_tokens: true, pre_segmented_query: false, @@ -153,8 +155,9 @@ defmodule OpenApiTypesense.MultiSearchCollectionParameters do conversation: :boolean, conversation_id: {:string, :generic}, conversation_model_id: {:string, :generic}, - drop_tokens_mode: {:string, :generic}, + drop_tokens_mode: {OpenApiTypesense.DropTokensMode, :t}, drop_tokens_threshold: :integer, + enable_analytics: :boolean, enable_overrides: :boolean, enable_synonyms: :boolean, enable_typos_for_alpha_numerical_tokens: :boolean, diff --git a/lib/open_api_typesense/schemas/multi_search_result_item.ex b/lib/open_api_typesense/schemas/multi_search_result_item.ex index c42935a..af32fc8 100644 --- a/lib/open_api_typesense/schemas/multi_search_result_item.ex +++ b/lib/open_api_typesense/schemas/multi_search_result_item.ex @@ -13,6 +13,7 @@ defmodule OpenApiTypesense.MultiSearchResultItem do found_docs: integer, grouped_hits: [OpenApiTypesense.SearchGroupedHit.t()], hits: [OpenApiTypesense.SearchResultHit.t()], + metadata: map, out_of: integer, page: integer, request_params: OpenApiTypesense.SearchRequestParams.t(), @@ -29,6 +30,7 @@ defmodule OpenApiTypesense.MultiSearchResultItem do :found_docs, :grouped_hits, :hits, + :metadata, :out_of, :page, :search_cutoff, @@ -88,6 +90,7 @@ defmodule OpenApiTypesense.MultiSearchResultItem do found_docs: :integer, grouped_hits: [{OpenApiTypesense.SearchGroupedHit, :t}], hits: [{OpenApiTypesense.SearchResultHit, :t}], + metadata: :map, out_of: :integer, page: :integer, request_params: {OpenApiTypesense.SearchRequestParams, :t}, diff --git a/lib/open_api_typesense/schemas/search_parameters.ex b/lib/open_api_typesense/schemas/search_parameters.ex index 96bbf6c..54cfdf4 100644 --- a/lib/open_api_typesense/schemas/search_parameters.ex +++ b/lib/open_api_typesense/schemas/search_parameters.ex @@ -9,8 +9,9 @@ defmodule OpenApiTypesense.SearchParameters do conversation: boolean, conversation_id: String.t(), conversation_model_id: String.t(), - drop_tokens_mode: String.t(), + drop_tokens_mode: OpenApiTypesense.DropTokensMode.t(), drop_tokens_threshold: integer, + enable_analytics: boolean, enable_highlight_v1: boolean, enable_overrides: boolean, enable_synonyms: boolean, @@ -69,6 +70,7 @@ defmodule OpenApiTypesense.SearchParameters do stopwords: String.t(), synonym_num_typos: integer, synonym_prefix: boolean, + synonym_sets: String.t(), text_match_type: String.t(), typo_tokens_threshold: integer, use_cache: boolean, @@ -134,12 +136,14 @@ defmodule OpenApiTypesense.SearchParameters do :stopwords, :synonym_num_typos, :synonym_prefix, + :synonym_sets, :text_match_type, :typo_tokens_threshold, :use_cache, :vector_query, :voice_query, - drop_tokens_mode: "right_to_left", + drop_tokens_mode: %OpenApiTypesense.DropTokensMode{}, + enable_analytics: true, enable_highlight_v1: true, enable_overrides: false, enable_typos_for_numerical_tokens: true, @@ -158,8 +162,9 @@ defmodule OpenApiTypesense.SearchParameters do conversation: :boolean, conversation_id: {:string, :generic}, conversation_model_id: {:string, :generic}, - drop_tokens_mode: {:string, :generic}, + drop_tokens_mode: {OpenApiTypesense.DropTokensMode, :t}, drop_tokens_threshold: :integer, + enable_analytics: :boolean, enable_highlight_v1: :boolean, enable_overrides: :boolean, enable_synonyms: :boolean, @@ -218,6 +223,7 @@ defmodule OpenApiTypesense.SearchParameters do stopwords: {:string, :generic}, synonym_num_typos: :integer, synonym_prefix: :boolean, + synonym_sets: {:string, :generic}, text_match_type: {:string, :generic}, typo_tokens_threshold: :integer, use_cache: :boolean, diff --git a/lib/open_api_typesense/schemas/search_result.ex b/lib/open_api_typesense/schemas/search_result.ex index 40a1714..944dc17 100644 --- a/lib/open_api_typesense/schemas/search_result.ex +++ b/lib/open_api_typesense/schemas/search_result.ex @@ -11,6 +11,7 @@ defmodule OpenApiTypesense.SearchResult do found_docs: integer, grouped_hits: [OpenApiTypesense.SearchGroupedHit.t()], hits: [OpenApiTypesense.SearchResultHit.t()], + metadata: map, out_of: integer, page: integer, request_params: OpenApiTypesense.SearchRequestParams.t(), @@ -25,6 +26,7 @@ defmodule OpenApiTypesense.SearchResult do :found_docs, :grouped_hits, :hits, + :metadata, :out_of, :page, :search_cutoff, @@ -82,6 +84,7 @@ defmodule OpenApiTypesense.SearchResult do found_docs: :integer, grouped_hits: [{OpenApiTypesense.SearchGroupedHit, :t}], hits: [{OpenApiTypesense.SearchResultHit, :t}], + metadata: :map, out_of: :integer, page: :integer, request_params: {OpenApiTypesense.SearchRequestParams, :t}, diff --git a/lib/open_api_typesense/schemas/voice_query_model_collection_config.ex b/lib/open_api_typesense/schemas/voice_query_model_collection_config.ex index 4cefa64..fff0a59 100644 --- a/lib/open_api_typesense/schemas/voice_query_model_collection_config.ex +++ b/lib/open_api_typesense/schemas/voice_query_model_collection_config.ex @@ -6,7 +6,7 @@ defmodule OpenApiTypesense.VoiceQueryModelCollectionConfig do @type t :: %__MODULE__{model_name: String.t()} - defstruct model_name: "" + defstruct [:model_name] @doc false @spec __fields__(atom) :: keyword diff --git a/priv/car_data.json b/priv/car_data.json new file mode 100644 index 0000000..d3c0f50 --- /dev/null +++ b/priv/car_data.json @@ -0,0 +1,222 @@ +[ + { + "city_mpg": 13, + "driven_wheels": "rear wheel drive", + "engine_cylinders": 8, + "engine_fuel_type": "premium unleaded (recommended)", + "engine_hp": 707, + "highway_mpg": 22, + "car_data_id": 1480, + "id": "1480", + "make": "Dodge", + "market_category": ["Factory Tuner", "High-Performance"], + "model": "Charger", + "msrp": 65945, + "number_of_doors": 4, + "popularity": 1851, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Large", + "vehicle_style": "Sedan", + "year": 2017 + }, + { + "city_mpg": 20, + "driven_wheels": "front wheel drive", + "engine_cylinders": 4, + "engine_fuel_type": "regular unleaded", + "engine_hp": 140, + "highway_mpg": 30, + "car_data_id": 1481, + "id": "1481", + "make": "Honda", + "market_category": ["Hatchback", "Crossover"], + "model": "Civic", + "msrp": 22500, + "number_of_doors": 4, + "popularity": 4500, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Compact", + "vehicle_style": "Hatchback", + "year": 2022 + }, + { + "city_mpg": 16, + "driven_wheels": "all wheel drive", + "engine_cylinders": 6, + "engine_fuel_type": "premium unleaded (required)", + "engine_hp": 335, + "highway_mpg": 24, + "car_data_id": 1482, + "id": "1482", + "make": "Audi", + "market_category": ["Luxury", "Performance"], + "model": "A6", + "msrp": 55900, + "number_of_doors": 4, + "popularity": 3106, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Midsize", + "vehicle_style": "Sedan", + "year": 2023 + }, + { + "city_mpg": 15, + "driven_wheels": "rear wheel drive", + "engine_cylinders": 8, + "engine_fuel_type": "premium unleaded (recommended)", + "engine_hp": 455, + "highway_mpg": 25, + "car_data_id": 1483, + "id": "1483", + "make": "Chevrolet", + "market_category": ["Muscle", "High-Performance"], + "model": "Camaro", + "msrp": 38000, + "number_of_doors": 2, + "popularity": 1385, + "transmission_type": "MANUAL", + "vehicle_size": "Midsize", + "vehicle_style": "Coupe", + "year": 2021 + }, + { + "city_mpg": 18, + "driven_wheels": "four wheel drive", + "engine_cylinders": 6, + "engine_fuel_type": "regular unleaded", + "engine_hp": 285, + "highway_mpg": 23, + "car_data_id": 1484, + "id": "1484", + "make": "Jeep", + "market_category": ["Crossover", "Off-road"], + "model": "Wrangler", + "msrp": 35000, + "number_of_doors": 4, + "popularity": 4200, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Compact", + "vehicle_style": "SUV", + "year": 2023 + }, + { + "city_mpg": 25, + "driven_wheels": "front wheel drive", + "engine_cylinders": 4, + "engine_fuel_type": "regular unleaded", + "engine_hp": 185, + "highway_mpg": 35, + "car_data_id": 1485, + "id": "1485", + "make": "Hyundai", + "market_category": ["Economy"], + "model": "Elantra", + "msrp": 20500, + "number_of_doors": 4, + "popularity": 1800, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Compact", + "vehicle_style": "Sedan", + "year": 2022 + }, + { + "city_mpg": 19, + "driven_wheels": "rear wheel drive", + "engine_cylinders": 6, + "engine_fuel_type": "premium unleaded (recommended)", + "engine_hp": 255, + "highway_mpg": 28, + "car_data_id": 1486, + "id": "1486", + "make": "BMW", + "market_category": ["Luxury", "Performance"], + "model": "3 Series", + "msrp": 42000, + "number_of_doors": 4, + "popularity": 3916, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Compact", + "vehicle_style": "Sedan", + "year": 2023 + }, + { + "city_mpg": 14, + "driven_wheels": "all wheel drive", + "engine_cylinders": 8, + "engine_fuel_type": "premium unleaded (required)", + "engine_hp": 560, + "highway_mpg": 21, + "car_data_id": 1487, + "id": "1487", + "make": "Porsche", + "market_category": ["Luxury", "High-Performance"], + "model": "Cayenne", + "msrp": 80000, + "number_of_doors": 4, + "popularity": 1715, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Midsize", + "vehicle_style": "SUV", + "year": 2022 + }, + { + "city_mpg": 22, + "driven_wheels": "front wheel drive", + "engine_cylinders": 4, + "engine_fuel_type": "regular unleaded", + "engine_hp": 200, + "highway_mpg": 31, + "car_data_id": 1488, + "id": "1488", + "make": "Volkswagen", + "market_category": ["Sport", "Hatchback"], + "model": "GTI", + "msrp": 31000, + "number_of_doors": 4, + "popularity": 873, + "transmission_type": "MANUAL", + "vehicle_size": "Compact", + "vehicle_style": "Hatchback", + "year": 2023 + }, + { + "city_mpg": 17, + "driven_wheels": "all wheel drive", + "engine_cylinders": 6, + "engine_fuel_type": "regular unleaded", + "engine_hp": 290, + "highway_mpg": 24, + "car_data_id": 1489, + "id": "1489", + "make": "Subaru", + "market_category": ["Off-road", "Family"], + "model": "Outback", + "msrp": 29000, + "number_of_doors": 4, + "popularity": 640, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Midsize", + "vehicle_style": "Wagon", + "year": 2022 + }, + { + "city_mpg": 28, + "driven_wheels": "front wheel drive", + "engine_cylinders": 4, + "engine_fuel_type": "regular unleaded", + "engine_hp": 160, + "highway_mpg": 38, + "car_data_id": 1490, + "id": "1490", + "make": "Toyota", + "market_category": ["Economy", "Hybrid"], + "model": "Prius", + "msrp": 26000, + "number_of_doors": 4, + "popularity": 2031, + "transmission_type": "AUTOMATIC", + "vehicle_size": "Compact", + "vehicle_style": "Hatchback", + "year": 2023 + } +] diff --git a/priv/open_api.yml b/priv/open_api.yml index 57942ab..2ecf471 100644 --- a/priv/open_api.yml +++ b/priv/open_api.yml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Typesense API description: "An open source search engine for building delightful search experiences." - version: '28.0' + version: '30.0' license: name: GPL-3.0 url: https://opensource.org/licenses/GPL-3.0 @@ -20,7 +20,7 @@ servers: default: "8108" description: The port of your Typesense server externalDocs: - description: Find out more about Typsesense + description: Find out more about Typesense url: https://typesense.org security: - api_key_header: [] @@ -1261,7 +1261,7 @@ paths: $ref: '#/components/schemas/ConversationModelCreateSchema' required: true responses: - '200': + '201': content: application/json: schema: @@ -1272,7 +1272,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ApiResponse" + $ref: '#/components/schemas/ApiResponse' '401': description: Missing API key content: @@ -1776,21 +1776,17 @@ paths: application/json: schema: $ref: "#/components/schemas/ApiResponse" - /operations/db/compact: + /operations/cache/clear: post: tags: - operations - summary: Compaction of the underlying RocksDB database + summary: Clear the cached responses of search requests in the LRU cache. description: - Typesense uses RocksDB to store your documents on the disk. If you do - frequent writes or updates, you could benefit from running a compaction - of the underlying RocksDB database. This could reduce the size of the - database and decrease read latency. While the database will not block - during this operation, we recommend running it during off-peak hours. - operationId: compact + Clear the cached responses of search requests that are sent with `use_cache` parameter in the LRU cache. + operationId: clearCache responses: '200': - description: Compacting the database + description: Clear cache succeeded. content: application/json: schema: @@ -1801,18 +1797,18 @@ paths: application/json: schema: $ref: "#/components/schemas/ApiResponse" - /operations/cache/clear: + /operations/db/compact: post: tags: - operations - summary: Clears the cache + summary: Compacting the on-disk database description: - Responses of search requests that are sent with use_cache parameter are - cached in a LRU cache. Clears cache completely. - operationId: clearCache + Typesense uses RocksDB to store your documents on the disk. If you do frequent writes or updates, you could benefit from running a compaction of the underlying RocksDB database. + This could reduce the size of the database and decrease read latency. While the database will not block during this operation, we recommend running it during off-peak hours. + operationId: compactDb responses: '200': - description: Cache cleared + description: Compacting the on-disk database succeeded. content: application/json: schema: @@ -1827,17 +1823,24 @@ paths: post: tags: - operations - summary: Enable logging of requests that take over a defined threshold of time. - description: >- - Slow requests are logged to the primary log file, with the prefix SLOW - REQUEST. Default is -1 which disables slow request logging. - operationId: config + summary: Toggle Slow Request Log + description: + Enable logging of requests that take over a defined threshold of time. + Default is `-1` which disables slow request logging. + Slow requests are logged to the primary log file, with the prefix SLOW REQUEST. + operationId: toggleSlowRequestLog requestBody: - description: Define threshold in ms for logging of requests content: application/json: schema: - $ref: "#/components/schemas/ConfigSchema" + type: object + properties: + log-slow-requests-time-ms: + type: integer + required: + - log-slow-requests-time-ms + example: | + {"log-slow-requests-time-ms": 2000} responses: '201': description: Updated slow request log @@ -1913,17 +1916,17 @@ paths: tags: - analytics summary: Create an analytics event - description: Sending events for analytics e.g rank search results based on popularity. + description: Submit a single analytics event. The event must correspond to an existing analytics rule by name. operationId: createAnalyticsEvent requestBody: - description: The Analytics event to be created + description: The analytics event to be created content: application/json: schema: - $ref: '#/components/schemas/AnalyticsEventCreateSchema' + $ref: '#/components/schemas/AnalyticsEvent' required: true responses: - '201': + '200': description: Analytics event successfully created content: application/json: @@ -1941,28 +1944,105 @@ paths: application/json: schema: $ref: "#/components/schemas/ApiResponse" + get: + tags: + - analytics + summary: Retrieve analytics events + description: Retrieve the most recent events for a user and rule. + operationId: getAnalyticsEvents + parameters: + - name: user_id + in: query + required: true + schema: + type: string + - name: name + in: query + description: Analytics rule name + required: true + schema: + type: string + - name: n + in: query + description: Number of events to return (max 1000) + required: true + schema: + type: integer + responses: + '200': + description: Events fetched + content: + application/json: + schema: + $ref: '#/components/schemas/AnalyticsEventsResponse' + '400': + description: Bad request, see error message for details + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + /analytics/flush: + post: + tags: + - analytics + summary: Flush in-memory analytics to disk + description: Triggers a flush of analytics data to persistent storage. + operationId: flushAnalytics + responses: + '200': + description: Flush triggered + content: + application/json: + schema: + $ref: '#/components/schemas/AnalyticsEventCreateResponse' + /analytics/status: + get: + tags: + - analytics + summary: Get analytics subsystem status + description: Returns sizes of internal analytics buffers and queues. + operationId: getAnalyticsStatus + responses: + '200': + description: Status fetched + content: + application/json: + schema: + $ref: '#/components/schemas/AnalyticsStatus' /analytics/rules: post: tags: - analytics - summary: Creates an analytics rule - description: - When an analytics rule is created, we give it a name and describe the type, the source collections and the destination collection. + summary: Create analytics rule(s) + description: Create one or more analytics rules. You can send a single rule object or an array of rule objects. operationId: createAnalyticsRule requestBody: - description: The Analytics rule to be created + description: The analytics rule(s) to be created content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRuleSchema" + oneOf: + - $ref: "#/components/schemas/AnalyticsRuleCreate" + - type: array + items: + $ref: "#/components/schemas/AnalyticsRuleCreate" required: true responses: - '201': - description: Analytics rule successfully created + '200': + description: Analytics rule(s) successfully created content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRuleSchema" + oneOf: + - $ref: "#/components/schemas/AnalyticsRule" + - type: array + items: + anyOf: + - $ref: "#/components/schemas/AnalyticsRule" + - type: object + properties: + error: + type: string '400': description: Bad request, see error message for details content: @@ -1984,17 +2064,25 @@ paths: get: tags: - analytics - summary: Retrieves all analytics rules - description: - Retrieve the details of all analytics rules + summary: Retrieve analytics rules + description: Retrieve all analytics rules. Use the optional rule_tag filter to narrow down results. operationId: retrieveAnalyticsRules + parameters: + - in: query + name: rule_tag + schema: + type: string + required: false + description: Filter rules by rule_tag responses: '200': description: Analytics rules fetched content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRulesRetrieveSchema" + type: array + items: + $ref: "#/components/schemas/AnalyticsRule" '401': description: "Missing API key" content: @@ -2021,7 +2109,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRuleUpsertSchema" + $ref: "#/components/schemas/AnalyticsRuleUpdate" required: true responses: '200': @@ -2029,7 +2117,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRuleSchema" + $ref: "#/components/schemas/AnalyticsRule" '400': description: Bad request, see error message for details content: @@ -2062,7 +2150,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRuleSchema" + $ref: "#/components/schemas/AnalyticsRule" '401': description: "Missing API key" content: @@ -2095,7 +2183,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AnalyticsRuleDeleteResponse" + $ref: "#/components/schemas/AnalyticsRule" '401': description: "Missing API key" content: @@ -2371,7 +2459,7 @@ paths: schema: $ref: '#/components/schemas/PresetSchema' '400': - description: Bad request, see error message for details. + description: Bad request, see error message for details content: application/json: schema: @@ -2566,18 +2654,24 @@ paths: application/json: schema: $ref: '#/components/schemas/NLSearchModelSchema' + '400': + description: Bad request, see error message for details + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' '401': description: "Missing API key" content: application/json: schema: $ref: "#/components/schemas/ApiResponse" - '400': - description: Bad request, see error message for details + '409': + description: Model already exists content: application/json: schema: - $ref: '#/components/schemas/ApiResponse' + $ref: "#/components/schemas/ApiResponse" /nl_search_models/{modelId}: get: tags: @@ -2736,6 +2830,12 @@ components: minLength: 1 maxLength: 1 default: [] + synonym_sets: + type: array + description: List of synonym set names to associate with this collection + items: + type: string + example: "synonym_set_1" enable_nested_fields: type: boolean description: @@ -2780,6 +2880,12 @@ components: facet: true items: $ref: "#/components/schemas/Field" + synonym_sets: + type: array + description: List of synonym set names to associate with this collection + items: + type: string + example: "synonym_set_1" metadata: type: object description: > @@ -2930,18 +3036,7 @@ components: properties: model_name: type: string - default: "" example: "ts/whisper/base.en" - ConfigSchema: - type: object - required: - - log_slow_requests_time_ms - properties: - log_slow_requests_time_ms: - type: integer - description: - Enable logging of requests that take over a defined threshold of - time in ms. CollectionAliasSchema: type: object required: @@ -3015,6 +3110,10 @@ components: description: Returned only for union query response. items: $ref: "#/components/schemas/SearchRequestParams" + metadata: + type: object + description: Custom JSON object that can be returned in the search response + additionalProperties: true SearchRequestParams: type: object required: @@ -3445,24 +3544,6 @@ components: x-go-type: "[]*ApiKey" items: $ref: "#/components/schemas/ApiKey" - ScopedKeyParameters: - type: object - properties: - filter_by: - type: string - expires_at: - type: integer - format: int64 - SnapshotParameters: - type: object - properties: - snapshot_path: - type: string - ErrorResponse: - type: object - properties: - message: - type: string MultiSearchResult: type: object required: @@ -3555,7 +3636,7 @@ components: filter_by: description: - Filter conditions for refining youropen api validator search results. Separate + Filter conditions for refining your open api validator search results. Separate multiple conditions with &&. type: string example: "num_employees:>100 && country: [USA, UK]" @@ -3677,12 +3758,24 @@ components: type: boolean default: true + enable_analytics: + description: > + Flag for enabling/disabling analytics aggregation for specific search + queries (for e.g. those originating from a test script). + type: boolean + default: true + snippet_threshold: description: > Field values under this length will be fully highlighted, instead of showing a snippet of relevant portion. Default: 30 type: integer + synonym_sets: + type: string + description: List of synonym set names to associate with this search query + example: "synonym_set_1,synonym_set_2" + drop_tokens_threshold: description: > If the number of results found for a specific query is less than @@ -3691,13 +3784,7 @@ components: are dropped first. Set to 0 to disable. Default: 10 type: integer drop_tokens_mode: - description: > - Dictates the direction in which the words in the query must be dropped when the original words in the query do not appear in any document. - Values: right_to_left (default), left_to_right, both_sides:3 - A note on both_sides:3 - for queries upto 3 tokens (words) in length, this mode will drop tokens from both sides and exhaustively rank all matching results. - If query length is greater than 3 words, Typesense will just fallback to default behavior of right_to_left - type: string - default: "right_to_left" + $ref: "#/components/schemas/DropTokensMode" typo_tokens_threshold: description: > If the number of results found for a specific query is less than this number, @@ -4074,13 +4161,7 @@ components: are dropped first. Set to 0 to disable. Default: 10 type: integer drop_tokens_mode: - description: > - Dictates the direction in which the words in the query must be dropped when the original words in the query do not appear in any document. - Values: right_to_left (default), left_to_right, both_sides:3 - A note on both_sides:3 - for queries upto 3 tokens (words) in length, this mode will drop tokens from both sides and exhaustively rank all matching results. - If query length is greater than 3 words, Typesense will just fallback to default behavior of right_to_left - type: string - default: "right_to_left" + $ref: "#/components/schemas/DropTokensMode" typo_tokens_threshold: description: > If the number of results found for a specific query is less than this number, @@ -4100,6 +4181,14 @@ components: type: boolean description: > If you have some synonyms defined but want to disable all of them for a particular search query, set enable_synonyms to false. Default: true + + enable_analytics: + description: > + Flag for enabling/disabling analytics aggregation for specific search + queries (for e.g. those originating from a test script). + type: boolean + default: true + synonym_prefix: type: boolean description: > @@ -4338,105 +4427,133 @@ components: properties: ok: type: boolean - AnalyticsEventCreateSchema: + AnalyticsEvent: type: object required: - - type - name + - event_type - data properties: - type: - type: string name: type: string + description: Name of the analytics rule this event corresponds to + event_type: + type: string + description: Type of event (e.g., click, conversion, query, visit) data: type: object - AnalyticsRuleUpsertSchema: - type: object - required: - - type - - params - properties: - type: - type: string - enum: - - popular_queries - - nohits_queries - - counter - params: - $ref: "#/components/schemas/AnalyticsRuleParameters" - AnalyticsRuleParameters: - type: object - required: - - source - - destination - properties: - source: - $ref: '#/components/schemas/AnalyticsRuleParametersSource' - destination: - $ref: '#/components/schemas/AnalyticsRuleParametersDestination' - limit: - type: integer - expand_query: - type: boolean - AnalyticsRuleParametersSource: + description: Event payload + properties: + user_id: + type: string + doc_id: + type: string + doc_ids: + type: array + items: + type: string + q: + type: string + analytics_tag: + type: string + AnalyticsEventsResponse: type: object required: - - collections + - events properties: - collections: - type: array - items: - type: string events: type: array items: type: object - required: - - type - - weight - - name properties: - type: - type: string - weight: - type: number - format: float - name: - type: string - AnalyticsRuleParametersDestination: + name: { type: string } + event_type: { type: string } + collection: { type: string } + timestamp: { type: integer, format: int64 } + user_id: { type: string } + doc_id: { type: string } + doc_ids: + type: array + items: { type: string } + query: { type: string } + AnalyticsRuleCreate: type: object required: + - name + - type - collection + - event_type properties: + name: + type: string + type: + type: string + enum: [popular_queries, nohits_queries, counter, log] collection: type: string - counter_field: + event_type: + type: string + rule_tag: type: string - AnalyticsRuleDeleteResponse: + params: + type: object + properties: + destination_collection: + type: string + limit: + type: integer + capture_search_requests: + type: boolean + meta_fields: + type: array + items: { type: string } + expand_query: + type: boolean + counter_field: + type: string + weight: + type: integer + AnalyticsRuleUpdate: type: object - required: - - name + description: Fields allowed to update on an analytics rule properties: name: type: string - AnalyticsRuleSchema: - allOf: - - $ref: '#/components/schemas/AnalyticsRuleUpsertSchema' - - type: object - required: - - name + rule_tag: + type: string + params: + type: object properties: - name: + destination_collection: type: string - AnalyticsRulesRetrieveSchema: + limit: + type: integer + capture_search_requests: + type: boolean + meta_fields: + type: array + items: { type: string } + expand_query: + type: boolean + counter_field: + type: string + weight: + type: integer + AnalyticsRule: + allOf: + - $ref: '#/components/schemas/AnalyticsRuleCreate' + - type: object + AnalyticsStatus: type: object properties: - rules: - type: array - items: - $ref: "#/components/schemas/AnalyticsRuleSchema" - x-go-type: '[]*AnalyticsRuleSchema' + popular_prefix_queries: { type: integer } + nohits_prefix_queries: { type: integer } + log_prefix_queries: { type: integer } + query_log_events: { type: integer } + query_counter_events: { type: integer } + doc_log_events: { type: integer } + doc_counter_events: { type: integer } + APIStatsResponse: type: object properties: @@ -4569,13 +4686,19 @@ components: type: string enum: [create, update, upsert, emplace] DropTokensMode: - type: string - enum: [right_to_left, left_to_right, both_sides:3] - description: > - Dictates the direction in which the words in the query must be dropped when the original words in the query do not appear in any document. - Values: right_to_left (default), left_to_right, both_sides:3 - A note on both_sides:3 - for queries upto 3 tokens (words) in length, this mode will drop tokens from both sides and exhaustively rank all matching results. - If query length is greater than 3 words, Typesense will just fallback to default behavior of right_to_left + type: object + properties: + match: + type: string + description: > + Dictates the direction in which the words in the query must be dropped when the original words in the query do not appear in any document. + Values: right_to_left (default), left_to_right, both_sides:3 + A note on both_sides:3 - for queries up to 3 tokens (words) in length, this mode will drop tokens from both sides and exhaustively rank all matching results. + If query length is greater than 3 words, Typesense will just fallback to default behavior of right_to_left + enum: + - right_to_left + - left_to_right + - both_sides:3 ConversationModelCreateSchema: required: - model_name @@ -4759,6 +4882,77 @@ components: type: string description: ID of the deleted NL search model + SynonymItemSchema: + type: object + required: + - id + - synonyms + properties: + id: + type: string + description: Unique identifier for the synonym item + synonyms: + type: array + description: Array of words that should be considered as synonyms + items: + type: string + root: + type: string + description: For 1-way synonyms, indicates the root word that words in the synonyms parameter map to + locale: + type: string + description: Locale for the synonym, leave blank to use the standard tokenizer + symbols_to_index: + type: array + description: By default, special characters are dropped from synonyms. Use this attribute to specify which special characters should be indexed as is + items: + type: string + + SynonymSetCreateSchema: + type: object + required: + - items + properties: + items: + type: array + description: Array of synonym items + items: + $ref: "#/components/schemas/SynonymItemSchema" + + SynonymSetSchema: + allOf: + - $ref: "#/components/schemas/SynonymSetCreateSchema" + - type: object + required: + - name + properties: + name: + type: string + description: Name of the synonym set + + SynonymSetsRetrieveSchema: + type: object + required: + - synonym_sets + properties: + synonym_sets: + type: array + description: Array of synonym sets + items: + $ref: "#/components/schemas/SynonymSetSchema" + + SynonymSetRetrieveSchema: + $ref: "#/components/schemas/SynonymSetCreateSchema" + + SynonymSetDeleteSchema: + type: object + required: + - name + properties: + name: + type: string + description: Name of the deleted synonym set + securitySchemes: api_key_header: type: apiKey diff --git a/test/operations/analytics_test.exs b/test/operations/analytics_test.exs index 6abf9b8..6671efe 100644 --- a/test/operations/analytics_test.exs +++ b/test/operations/analytics_test.exs @@ -2,6 +2,7 @@ defmodule AnalyticsTest do use ExUnit.Case, async: true alias OpenApiTypesense.Analytics + alias OpenApiTypesense.AnalyticsRule alias OpenApiTypesense.AnalyticsRuleSchema alias OpenApiTypesense.AnalyticsRulesRetrieveSchema alias OpenApiTypesense.ApiResponse @@ -126,16 +127,16 @@ defmodule AnalyticsTest do } } - assert {:ok, %AnalyticsRuleSchema{name: ^name}} = + assert {:ok, %AnalyticsRule{name: ^name}} = Analytics.upsert_analytics_rule(name, body) - assert {:ok, %AnalyticsRuleSchema{name: ^name}} = + assert {:ok, %AnalyticsRule{name: ^name}} = Analytics.upsert_analytics_rule(name, body, []) - assert {:ok, %AnalyticsRuleSchema{name: ^name}} = + assert {:ok, %AnalyticsRule{name: ^name}} = Analytics.upsert_analytics_rule(name, body, conn: conn) - assert {:ok, %AnalyticsRuleSchema{name: ^name}} = + assert {:ok, %AnalyticsRule{name: ^name}} = Analytics.upsert_analytics_rule(name, body, conn: map_conn) end diff --git a/test/operations/collections_test.exs b/test/operations/collections_test.exs index 59dc748..f507402 100644 --- a/test/operations/collections_test.exs +++ b/test/operations/collections_test.exs @@ -171,7 +171,7 @@ defmodule CollectionsTest do @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existing alias", %{conn: conn, map_conn: map_conn} do assert Collections.get_alias("non-existing-alias") == - {:error, %ApiResponse{message: "Not Found"}} + {:error, %ApiResponse{message: "Collection not found"}} assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", []) assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", conn: conn) @@ -181,7 +181,7 @@ defmodule CollectionsTest do @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existing collection", %{conn: conn, map_conn: map_conn} do assert Collections.get_collection("non-existing-collection") == - {:error, %ApiResponse{message: "Not Found"}} + {:error, %ApiResponse{message: "Collection not found"}} assert {:error, %ApiResponse{message: _}} = Collections.get_collection("xyz") assert {:error, %ApiResponse{message: _}} = Collections.get_collection("xyz", []) diff --git a/test/operations/nl_search_models_test.exs b/test/operations/nl_search_models_test.exs index 0e08efd..8057a8a 100644 --- a/test/operations/nl_search_models_test.exs +++ b/test/operations/nl_search_models_test.exs @@ -1,95 +1,23 @@ defmodule NlSearchModelsTest do use ExUnit.Case, async: true - alias OpenApiTypesense.ApiKey - alias OpenApiTypesense.ApiKeysResponse - alias OpenApiTypesense.ApiKeyDeleteResponse - alias OpenApiTypesense.Connection - alias OpenApiTypesense.NlSearchModels + # alias OpenApiTypesense.Connection + # alias OpenApiTypesense.NlSearchModels setup_all do - conn = Connection.new() - map_conn = %{api_key: "xyz", host: "localhost", port: 8108, scheme: "http"} + # conn = Connection.new() + # map_conn = %{api_key: "xyz", host: "localhost", port: 8108, scheme: "http"} - api_key_schema = %{ - actions: ["documents:search"], - collections: ["companies"], - description: "Search-only companies key" - } + # on_exit(fn -> + # end) - on_exit(fn -> - {:ok, %ApiKeysResponse{keys: keys}} = Keys.get_keys() - - keys - |> Enum.each(fn key -> - {:ok, %ApiKeyDeleteResponse{}} = Keys.delete_key(key.id) - end) - end) - - %{api_key_schema: api_key_schema, conn: conn, map_conn: map_conn} - end - - @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] - test "success: get a specific key", %{ - api_key_schema: api_key_schema, - conn: conn, - map_conn: map_conn - } do - assert {:ok, api_key} = Keys.create_key(api_key_schema) - - key_id = api_key.id - - assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id) - assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, []) - assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, conn: conn) - assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, conn: map_conn) - end - - @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] - test "success: list API keys", %{conn: conn, map_conn: map_conn} do - assert {:ok, %ApiKeysResponse{keys: keys}} = Keys.get_keys() - assert length(keys) >= 0 - - assert {:ok, %ApiKeysResponse{}} = Keys.get_keys([]) - assert {:ok, %ApiKeysResponse{}} = Keys.get_keys(conn: conn) - assert {:ok, %ApiKeysResponse{}} = Keys.get_keys(conn: map_conn) - end - - @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] - test "success: delete an API key", %{ - api_key_schema: api_key_schema, - conn: conn, - map_conn: map_conn - } do - assert {:ok, api_key} = Keys.create_key(api_key_schema) - - key_id = api_key.id - - assert {:ok, %ApiKeyDeleteResponse{id: ^key_id}} = Keys.delete_key(key_id) - assert {:error, _} = Keys.delete_key(key_id, []) - assert {:error, _} = Keys.delete_key(key_id, conn: conn) - assert {:error, _} = Keys.delete_key(key_id, conn: map_conn) - end - - @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] - test "success: create an search-only API key", %{ - api_key_schema: api_key_schema, - conn: conn, - map_conn: map_conn - } do - assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema) - assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, []) - assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, conn: conn) - assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, conn: map_conn) + # %{conn: conn, map_conn: map_conn} + :ok end - @tag ["29.0": true, "28.0": false, "27.1": false, "27.0": false, "26.0": false] - test "success: create an admin API key", %{api_key_schema: api_key_schema} do - body = - api_key_schema - |> Map.put(:actions, ["*"]) - |> Map.put(:collections, ["*"]) - - assert {:ok, %ApiKey{}} = Keys.create_key(body) + @tag [nls: true] + # test "success: get a specific key", %{conn: conn, map_conn: map_conn} do + test "success: get a specific key" do + assert 1 === false end end diff --git a/test/operations/operations_test.exs b/test/operations/operations_test.exs index 9715918..96fcf4a 100644 --- a/test/operations/operations_test.exs +++ b/test/operations/operations_test.exs @@ -34,12 +34,16 @@ defmodule OperationsTest do @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: toggle threshold time for request log", %{conn: conn, map_conn: map_conn} do assert {:ok, %SuccessStatus{success: true}} = - Operations.config(%{"log_slow_requests_time_ms" => 2_000}) + Operations.toggle_slow_request_log(%{"log-slow-requests-time-ms" => 2_000}) - body = %{"log_slow_requests_time_ms" => -1} - assert {:ok, %SuccessStatus{success: true}} = Operations.config(body, []) - assert {:ok, %SuccessStatus{success: true}} = Operations.config(body, conn: conn) - assert {:ok, %SuccessStatus{success: true}} = Operations.config(body, conn: map_conn) + body = %{"log-slow-requests-time-ms" => -1} + assert {:ok, %SuccessStatus{success: true}} = Operations.toggle_slow_request_log(body, []) + + assert {:ok, %SuccessStatus{success: true}} = + Operations.toggle_slow_request_log(body, conn: conn) + + assert {:ok, %SuccessStatus{success: true}} = + Operations.toggle_slow_request_log(body, conn: map_conn) end @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] @@ -52,10 +56,10 @@ defmodule OperationsTest do @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: compact database", %{conn: conn, map_conn: map_conn} do - assert {:ok, %SuccessStatus{success: true}} = Operations.compact() - assert {:ok, %SuccessStatus{success: true}} = Operations.compact([]) - assert {:ok, %SuccessStatus{success: true}} = Operations.compact(conn: conn) - assert {:ok, %SuccessStatus{success: true}} = Operations.compact(conn: map_conn) + assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db() + assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db([]) + assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db(conn: conn) + assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db(conn: map_conn) end @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] diff --git a/test/operations/override_test.exs b/test/operations/override_test.exs index ab0f91a..4c67c22 100644 --- a/test/operations/override_test.exs +++ b/test/operations/override_test.exs @@ -14,7 +14,7 @@ defmodule OverrideTest do @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: retrieve an override", %{conn: conn, map_conn: map_conn} do - assert {:error, %ApiResponse{message: "Not Found"}} = + assert {:error, %ApiResponse{message: "Collection not found"}} = Override.get_search_override("helmets", "custom-helmet") assert {:error, _} = Override.get_search_override("helmets", "custom-helmet", []) From fd8530ad930831f8bf1e8975d0432f84258b9cf2 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Sun, 2 Nov 2025 22:27:43 +0800 Subject: [PATCH 3/6] include v29.0 for all test cases --- .github/workflows/ci_v26.0.yml | 8 ++++-- .github/workflows/ci_v27.0.yml | 8 ++++-- .github/workflows/ci_v27.1.yml | 8 ++++-- .github/workflows/ci_v28.0.yml | 8 ++++-- .github/workflows/ci_v29.0.yml | 8 ++++-- .github/workflows/llm.yml | 16 +++++++---- test/connection_test.exs | 18 ++++++------ test/custom_client_test.exs | 6 ++-- test/default_client_test.exs | 6 ++-- test/operations/collections_test.exs | 18 ++++++------ test/operations/conversations_test.exs | 10 +++---- test/operations/curation_test.exs | 6 ++-- test/operations/debug_test.exs | 4 +-- test/operations/documents_test.exs | 39 +++++++++++++------------- test/operations/health_test.exs | 8 +++--- test/operations/join_test.exs | 16 +++++------ test/operations/keys_test.exs | 10 +++---- test/operations/operations_test.exs | 16 +++++------ test/operations/override_test.exs | 10 +++++++ test/operations/presets_test.exs | 8 +++--- test/operations/stopwords_test.exs | 10 +++---- test/operations/synonyms_test.exs | 8 +++--- 22 files changed, 142 insertions(+), 107 deletions(-) diff --git a/.github/workflows/ci_v26.0.yml b/.github/workflows/ci_v26.0.yml index 3048581..a60cad5 100644 --- a/.github/workflows/ci_v26.0.yml +++ b/.github/workflows/ci_v26.0.yml @@ -1,11 +1,13 @@ name: CI v26.0 on: - push: - branches: ["main"] pull_request: branches: ["main"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs # Workflows that would otherwise be triggered using `on: push` or @@ -19,7 +21,9 @@ jobs: # - [actions skip] test: + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && github.repository == 'jaeyson/open_api_typesense' }} runs-on: ubuntu-latest + environment: review env: MIX_ENV: test diff --git a/.github/workflows/ci_v27.0.yml b/.github/workflows/ci_v27.0.yml index 9a2f3db..afd60cc 100644 --- a/.github/workflows/ci_v27.0.yml +++ b/.github/workflows/ci_v27.0.yml @@ -1,11 +1,13 @@ name: CI v27.0 on: - push: - branches: ["main"] pull_request: branches: ["main"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs # Workflows that would otherwise be triggered using `on: push` or @@ -19,7 +21,9 @@ jobs: # - [actions skip] test: + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && github.repository == 'jaeyson/open_api_typesense' }} runs-on: ubuntu-latest + environment: review env: MIX_ENV: test diff --git a/.github/workflows/ci_v27.1.yml b/.github/workflows/ci_v27.1.yml index 084af47..7a47b32 100644 --- a/.github/workflows/ci_v27.1.yml +++ b/.github/workflows/ci_v27.1.yml @@ -1,11 +1,13 @@ name: CI v27.1 on: - push: - branches: ["main"] pull_request: branches: ["main"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs # Workflows that would otherwise be triggered using `on: push` or @@ -19,7 +21,9 @@ jobs: # - [actions skip] test: + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && github.repository == 'jaeyson/open_api_typesense' }} runs-on: ubuntu-latest + environment: review env: MIX_ENV: test diff --git a/.github/workflows/ci_v28.0.yml b/.github/workflows/ci_v28.0.yml index 1be96fc..36f42c7 100644 --- a/.github/workflows/ci_v28.0.yml +++ b/.github/workflows/ci_v28.0.yml @@ -1,11 +1,13 @@ name: CI v28.0 on: - push: - branches: ["main"] pull_request: branches: ["main"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs # Workflows that would otherwise be triggered using `on: push` or @@ -19,7 +21,9 @@ jobs: # - [actions skip] test: + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && github.repository == 'jaeyson/open_api_typesense' }} runs-on: ubuntu-latest + environment: review env: MIX_ENV: test diff --git a/.github/workflows/ci_v29.0.yml b/.github/workflows/ci_v29.0.yml index 55ae5af..3ad8ae6 100644 --- a/.github/workflows/ci_v29.0.yml +++ b/.github/workflows/ci_v29.0.yml @@ -1,11 +1,13 @@ name: CI v29.0 on: - push: - branches: ["main"] pull_request: branches: ["main"] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs # Workflows that would otherwise be triggered using `on: push` or @@ -19,7 +21,9 @@ jobs: # - [actions skip] test: + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && github.repository == 'jaeyson/open_api_typesense' }} runs-on: ubuntu-latest + environment: review env: MIX_ENV: test diff --git a/.github/workflows/llm.yml b/.github/workflows/llm.yml index b67276e..32379b2 100644 --- a/.github/workflows/llm.yml +++ b/.github/workflows/llm.yml @@ -1,17 +1,21 @@ name: LLM on: - push: - push: - branches: ["main"] - pull_request: - branches: ["main"] + pull_request: + branches: ["main"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: ci_workflow: uses: ./.github/workflows/ci_v29.0.yml secrets: inherit - staging_deploy: + llm: + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && github.repository == 'jaeyson/open_api_typesense' }} runs-on: ubuntu-latest + environment: review needs: [ci_workflow] env: diff --git a/test/connection_test.exs b/test/connection_test.exs index e4b206c..5255f6e 100644 --- a/test/connection_test.exs +++ b/test/connection_test.exs @@ -11,7 +11,7 @@ defmodule ConnectionTest do message: "Forbidden - a valid `x-typesense-api-key` header must be sent." } - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "new/0 using the default config to creates a connection struct" do assert Connection.new() === %Connection{ api_key: "xyz", @@ -25,7 +25,7 @@ defmodule ConnectionTest do } end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "new/1 with custom fields creates a connection struct" do conn = Connection.new(%{ @@ -47,7 +47,7 @@ defmodule ConnectionTest do } end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: wrong api key was configured" do conn = %{ host: "localhost", @@ -59,7 +59,7 @@ defmodule ConnectionTest do assert {:error, @forbidden} == Collections.get_collections(conn: conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: overriding config with a wrong API key" do conn = %{ host: "localhost", @@ -71,34 +71,34 @@ defmodule ConnectionTest do assert {:error, @forbidden} = Collections.get_collections(conn: conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: health check, with incorrect port number" do conn = %{api_key: "xyz", host: "localhost", port: 8100, scheme: "http"} assert {:error, "connection refused"} = Health.health(conn: conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: health check, with incorrect host" do conn = %{api_key: "xyz", host: "my_test_host", port: 8108, scheme: "http"} assert {:error, "non-existing domain"} = Health.health(conn: conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "new/1 with Connection struct" do conn = Connection.new() assert %Connection{} = Connection.new(conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "new/1 with empty map raises ArgumentError" do error = assert_raise ArgumentError, fn -> Connection.new(%{}) end assert error.message === "Missing required fields: [:api_key, :host, :port, :scheme]" end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "new/1 with invalid data type raises ArgumentError" do invalid_inputs = [ nil, diff --git a/test/custom_client_test.exs b/test/custom_client_test.exs index feb9578..2c7ce83 100644 --- a/test/custom_client_test.exs +++ b/test/custom_client_test.exs @@ -74,7 +74,7 @@ defmodule CustomClientTest do end) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "returns the configured options" do Application.put_env(:open_api_typesense, :options, finch: MyApp.CustomFinch, @@ -86,7 +86,7 @@ defmodule CustomClientTest do assert options === [finch: MyApp.CustomFinch, receive_timeout: 5_000] end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "returns an empty map if options is not configured" do Application.delete_env(:open_api_typesense, :options) @@ -95,7 +95,7 @@ defmodule CustomClientTest do assert options === nil end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "use another HTTP client" do map_conn = %{ api_key: "xyz", diff --git a/test/default_client_test.exs b/test/default_client_test.exs index 041ed7a..d125da8 100644 --- a/test/default_client_test.exs +++ b/test/default_client_test.exs @@ -7,7 +7,7 @@ defmodule DefaultClientTest do require Logger describe "request/2" do - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "default to req http client if no custom client set" do conn = Connection.new() @@ -27,7 +27,7 @@ defmodule DefaultClientTest do end describe "build_req_client/2" do - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "override req options through req field" do req = Client.build_req_client(Connection.new(), @@ -43,7 +43,7 @@ defmodule DefaultClientTest do end end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "get api key" do assert "xyz" = Client.api_key() end diff --git a/test/operations/collections_test.exs b/test/operations/collections_test.exs index f507402..d231b22 100644 --- a/test/operations/collections_test.exs +++ b/test/operations/collections_test.exs @@ -32,7 +32,7 @@ defmodule CollectionsTest do %{schema: schema, alias_name: "foo_bar", conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: clone a collection schema" do schema = %{ "name" => "vehicles", @@ -56,7 +56,7 @@ defmodule CollectionsTest do assert {:ok, _} = Collections.delete_collection(payload["name"]) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: create a collection", %{schema: schema, conn: conn, map_conn: map_conn} do name = schema["name"] @@ -72,7 +72,7 @@ defmodule CollectionsTest do Collections.get_collections() end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list collections", %{conn: conn, map_conn: map_conn} do assert {:ok, collections} = Collections.get_collections() assert length(collections) >= 0 @@ -85,7 +85,7 @@ defmodule CollectionsTest do assert {:ok, _} = Collections.get_collections(conn: map_conn, limit: 1) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: update an existing collection", %{conn: conn, map_conn: map_conn} do name = "burgers" @@ -118,7 +118,7 @@ defmodule CollectionsTest do Collections.delete_collection(name) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list empty aliases", %{conn: conn, map_conn: map_conn} do assert {:ok, %CollectionAliasesResponse{aliases: aliases}} = Collections.get_aliases() assert length(aliases) >= 0 @@ -127,7 +127,7 @@ defmodule CollectionsTest do assert {:ok, _} = Collections.get_aliases(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete a missing collection", %{conn: conn, map_conn: map_conn} do assert Collections.delete_collection("non-existing-collection") == {:error, @@ -142,7 +142,7 @@ defmodule CollectionsTest do Collections.delete_collection("xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: upsert an alias", %{ schema: schema, alias_name: alias_name, @@ -168,7 +168,7 @@ defmodule CollectionsTest do Collections.delete_alias(alias_name, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existing alias", %{conn: conn, map_conn: map_conn} do assert Collections.get_alias("non-existing-alias") == {:error, %ApiResponse{message: "Collection not found"}} @@ -178,7 +178,7 @@ defmodule CollectionsTest do assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existing collection", %{conn: conn, map_conn: map_conn} do assert Collections.get_collection("non-existing-collection") == {:error, %ApiResponse{message: "Collection not found"}} diff --git a/test/operations/conversations_test.exs b/test/operations/conversations_test.exs index db7c3ed..8805f63 100644 --- a/test/operations/conversations_test.exs +++ b/test/operations/conversations_test.exs @@ -34,7 +34,7 @@ defmodule ConversationsTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list conversation models", %{conn: conn, map_conn: map_conn} do assert {:ok, models} = Conversations.retrieve_all_conversation_models() assert length(models) >= 0 @@ -43,7 +43,7 @@ defmodule ConversationsTest do assert {:ok, _} = Conversations.retrieve_all_conversation_models(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existent conversation model", %{conn: conn, map_conn: map_conn} do assert {:error, %ApiResponse{message: "Model not found"}} = Conversations.retrieve_conversation_model("non-existent") @@ -53,7 +53,7 @@ defmodule ConversationsTest do assert {:error, _} = Conversations.retrieve_conversation_model("xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete a conversation model", %{conn: conn, map_conn: map_conn} do assert {:error, %ApiResponse{message: "Model not found"}} = Conversations.delete_conversation_model("non-existent") @@ -63,7 +63,7 @@ defmodule ConversationsTest do assert {:error, _} = Conversations.delete_conversation_model("xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: create a conversation model with incorrect API key", %{ conn: conn, map_conn: map_conn @@ -94,7 +94,7 @@ defmodule ConversationsTest do assert {:error, _} = Conversations.create_conversation_model(body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: update a conversation model with incorrect API key", %{ conn: conn, map_conn: map_conn diff --git a/test/operations/curation_test.exs b/test/operations/curation_test.exs index 6832322..433f8ca 100644 --- a/test/operations/curation_test.exs +++ b/test/operations/curation_test.exs @@ -34,7 +34,7 @@ defmodule CurationTest do %{schema_name: name, conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: upsert search override", %{ schema_name: schema_name, conn: conn, @@ -67,7 +67,7 @@ defmodule CurationTest do Curation.upsert_search_override(schema_name, override_id, body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete search override", %{ schema_name: schema_name, conn: conn, @@ -83,7 +83,7 @@ defmodule CurationTest do assert {:error, _} = Curation.delete_search_override(schema_name, "test", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list collection overrides", %{ schema_name: schema_name, conn: conn, diff --git a/test/operations/debug_test.exs b/test/operations/debug_test.exs index fe2c6bf..14ebaff 100644 --- a/test/operations/debug_test.exs +++ b/test/operations/debug_test.exs @@ -12,7 +12,7 @@ defmodule DebugTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list stopwords sets", %{conn: conn, map_conn: map_conn} do assert {:ok, %Debug{version: _}} = Debug.debug() assert {:ok, _} = Debug.debug([]) @@ -20,7 +20,7 @@ defmodule DebugTest do assert {:ok, _} = Debug.debug(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "field" do assert [version: {:string, :generic}] = Debug.__fields__(:debug_200_json_resp) end diff --git a/test/operations/documents_test.exs b/test/operations/documents_test.exs index a869c06..894d839 100644 --- a/test/operations/documents_test.exs +++ b/test/operations/documents_test.exs @@ -38,7 +38,7 @@ defmodule DocumentsTest do %{coll_name: name, conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: update a document", %{coll_name: coll_name} do body = %{ "shoes_id" => 12_299, @@ -57,7 +57,7 @@ defmodule DocumentsTest do Documents.update_document(coll_name, document_id, body) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: update a non-existent document", %{ coll_name: coll_name, conn: conn, @@ -81,7 +81,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.update_document(coll_name, document_id, body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: search a document", %{coll_name: coll_name, conn: conn, map_conn: map_conn} do body = [ @@ -123,7 +123,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.search_collection(coll_name, List.flatten([conn: map_conn], opts)) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: update non-existent documents", %{ coll_name: coll_name, conn: conn, @@ -145,7 +145,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.import_documents(coll_name, body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: multi-search with no documents", %{conn: conn, map_conn: map_conn} do body = %{ @@ -170,7 +170,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.multi_search(body, List.flatten([conn: map_conn], params)) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: update documents by query", %{ coll_name: coll_name, conn: conn, @@ -228,7 +228,7 @@ defmodule DocumentsTest do ) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: importing empty documents", %{coll_name: coll_name} do assert {:ok, ""} = Documents.import_documents(coll_name, []) @@ -239,8 +239,8 @@ defmodule DocumentsTest do Documents.import_documents(coll_name, [%{}]) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] - test "success: import documents where payload is type of string", %{coll_name: coll_name} do + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] + test "success: import documents where payload is JSONL", %{coll_name: coll_name} do body = [ %{ @@ -275,6 +275,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.import_documents(coll_name, body) end + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: import documents", %{coll_name: coll_name} do body = [ @@ -309,7 +310,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.import_documents(coll_name, body, action: "create") end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: index a document", %{coll_name: coll_name, conn: conn, map_conn: map_conn} do shoes_id = 220 @@ -333,7 +334,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.index_document(coll_name, body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list collection overrides", %{ coll_name: coll_name, conn: conn, @@ -352,7 +353,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.get_search_overrides("xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existent override", %{ coll_name: coll_name, conn: conn, @@ -366,7 +367,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.get_search_override(coll_name, "xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: delete a non-existent override", %{ coll_name: coll_name, conn: conn, @@ -380,7 +381,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.delete_search_override(coll_name, "xyz", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete a document", %{coll_name: coll_name, conn: conn, map_conn: map_conn} do shoes_id = 420 @@ -412,7 +413,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.delete_document(coll_name, id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete all documents", %{coll_name: coll_name, conn: conn, map_conn: map_conn} do body = [ @@ -456,7 +457,7 @@ defmodule DocumentsTest do assert {:ok, _} = Documents.delete_documents(coll_name, List.flatten([conn: map_conn], opts)) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existent document", %{ coll_name: coll_name, conn: conn, @@ -473,7 +474,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.get_document(coll_name, document_id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: export document from a non-existent collection", %{conn: conn, map_conn: map_conn} do opts = [exclude_fields: "fields"] @@ -487,7 +488,7 @@ defmodule DocumentsTest do assert {:error, _} = Documents.export_documents("xyz", List.flatten([conn: map_conn], opts)) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: upsert a search override", %{ coll_name: coll_name, conn: conn, @@ -520,7 +521,7 @@ defmodule DocumentsTest do Documents.upsert_search_override(coll_name, "customize-apple", body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "field" do assert [num_deleted: :integer] = Documents.__fields__(:delete_documents_200_json_resp) assert [num_updated: :integer] = Documents.__fields__(:update_documents_200_json_resp) diff --git a/test/operations/health_test.exs b/test/operations/health_test.exs index 00a8d99..b9ef9f4 100644 --- a/test/operations/health_test.exs +++ b/test/operations/health_test.exs @@ -12,7 +12,7 @@ defmodule HealthTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: health check", %{conn: conn, map_conn: map_conn} do assert {:ok, %HealthStatus{ok: true}} = Health.health() assert {:ok, %HealthStatus{ok: true}} = Health.health([]) @@ -20,7 +20,7 @@ defmodule HealthTest do assert {:ok, %HealthStatus{ok: true}} = Health.health(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: health check timeout" do conn = Connection.new(%{ @@ -33,7 +33,7 @@ defmodule HealthTest do assert {:error, "timeout"} = Health.health(conn: conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: health check connection refused" do conn = Connection.new(%{ @@ -46,7 +46,7 @@ defmodule HealthTest do assert {:error, "connection refused"} = Health.health(conn: conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: health check non-existing domain" do conn = %{ api_key: "wrong_key", diff --git a/test/operations/join_test.exs b/test/operations/join_test.exs index 543bceb..ad2fdd1 100644 --- a/test/operations/join_test.exs +++ b/test/operations/join_test.exs @@ -346,7 +346,7 @@ defmodule JoinTest do :ok end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: one-to-one relation" do searches = %{ searches: [ @@ -379,7 +379,7 @@ defmodule JoinTest do }} = Documents.multi_search(searches) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: one-to-many relation (simple)" do searches = %{ searches: [ @@ -437,7 +437,7 @@ defmodule JoinTest do }} = Documents.multi_search(searches) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: one-to-many relation (specialized)" do searches = %{ searches: [ @@ -474,7 +474,7 @@ defmodule JoinTest do }} = Documents.multi_search(searches) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: merging or nesting joined fields" do searches = %{ searches: [ @@ -515,7 +515,7 @@ defmodule JoinTest do }} = Documents.multi_search(searches) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: forcing nested array for joined fields" do searches = %{ searches: [ @@ -553,7 +553,7 @@ defmodule JoinTest do }} = Documents.multi_search(searches) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: left join" do opts = [ collection: "authors", @@ -578,7 +578,7 @@ defmodule JoinTest do }} = Documents.search_collection("authors", opts) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: nested joins" do opts = [ q: "shampoo", @@ -635,7 +635,7 @@ defmodule JoinTest do }} = Documents.search_collection("join_products", opts) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: nested joins (geo radius)" do opts = [ q: "shampoo", diff --git a/test/operations/keys_test.exs b/test/operations/keys_test.exs index 1857d02..f28ecb0 100644 --- a/test/operations/keys_test.exs +++ b/test/operations/keys_test.exs @@ -29,7 +29,7 @@ defmodule KeysTest do %{api_key_schema: api_key_schema, conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: get a specific key", %{ api_key_schema: api_key_schema, conn: conn, @@ -45,7 +45,7 @@ defmodule KeysTest do assert {:ok, %ApiKey{id: ^key_id}} = Keys.get_key(key_id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list API keys", %{conn: conn, map_conn: map_conn} do assert {:ok, %ApiKeysResponse{keys: keys}} = Keys.get_keys() assert length(keys) >= 0 @@ -55,7 +55,7 @@ defmodule KeysTest do assert {:ok, %ApiKeysResponse{}} = Keys.get_keys(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete an API key", %{ api_key_schema: api_key_schema, conn: conn, @@ -71,7 +71,7 @@ defmodule KeysTest do assert {:error, _} = Keys.delete_key(key_id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: create an search-only API key", %{ api_key_schema: api_key_schema, conn: conn, @@ -83,7 +83,7 @@ defmodule KeysTest do assert {:ok, %ApiKey{}} = Keys.create_key(api_key_schema, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: create an admin API key", %{api_key_schema: api_key_schema} do body = api_key_schema diff --git a/test/operations/operations_test.exs b/test/operations/operations_test.exs index 96fcf4a..05d35bf 100644 --- a/test/operations/operations_test.exs +++ b/test/operations/operations_test.exs @@ -15,7 +15,7 @@ defmodule OperationsTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: retrieve api stats", %{conn: conn, map_conn: map_conn} do assert {:ok, %APIStatsResponse{}} = Operations.retrieve_api_stats() assert {:ok, %APIStatsResponse{}} = Operations.retrieve_api_stats([]) @@ -23,7 +23,7 @@ defmodule OperationsTest do assert {:ok, %APIStatsResponse{}} = Operations.retrieve_api_stats(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: retrieve metrics", %{conn: conn, map_conn: map_conn} do assert {:ok, %{system_cpu_active_percentage: _}} = Operations.retrieve_metrics() assert {:ok, %{system_cpu_active_percentage: _}} = Operations.retrieve_metrics([]) @@ -31,7 +31,7 @@ defmodule OperationsTest do assert {:ok, %{system_cpu_active_percentage: _}} = Operations.retrieve_metrics(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: toggle threshold time for request log", %{conn: conn, map_conn: map_conn} do assert {:ok, %SuccessStatus{success: true}} = Operations.toggle_slow_request_log(%{"log-slow-requests-time-ms" => 2_000}) @@ -46,7 +46,7 @@ defmodule OperationsTest do Operations.toggle_slow_request_log(body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: clear cache", %{conn: conn, map_conn: map_conn} do assert {:ok, %SuccessStatus{success: true}} = Operations.clear_cache() assert {:ok, %SuccessStatus{success: true}} = Operations.clear_cache([]) @@ -54,7 +54,7 @@ defmodule OperationsTest do assert {:ok, %SuccessStatus{success: true}} = Operations.clear_cache(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: compact database", %{conn: conn, map_conn: map_conn} do assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db() assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db([]) @@ -62,7 +62,7 @@ defmodule OperationsTest do assert {:ok, %SuccessStatus{success: true}} = Operations.compact_db(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: take snapshot", %{conn: conn, map_conn: map_conn} do # we have to add sleep timer for github actions # otherwise it will return like: @@ -86,7 +86,7 @@ defmodule OperationsTest do Operations.take_snapshot(List.flatten([conn: map_conn], params)) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: re-elect leader", %{conn: conn, map_conn: map_conn} do assert {:ok, %SuccessStatus{success: false}} = Operations.vote() assert {:ok, %SuccessStatus{success: false}} = Operations.vote([]) @@ -94,7 +94,7 @@ defmodule OperationsTest do assert {:ok, %SuccessStatus{success: false}} = Operations.vote(conn: map_conn) end - @tag ["28.0": true, "27.1": false, "27.0": false, "26.0": false] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: get schema changes", %{conn: conn, map_conn: map_conn} do assert {:ok, schemas} = Operations.get_schema_changes() assert length(schemas) >= 0 diff --git a/test/operations/override_test.exs b/test/operations/override_test.exs index 4c67c22..3b61b4e 100644 --- a/test/operations/override_test.exs +++ b/test/operations/override_test.exs @@ -14,6 +14,16 @@ defmodule OverrideTest do @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: retrieve an override", %{conn: conn, map_conn: map_conn} do + assert {:error, %ApiResponse{message: "Not Found"}} = + Override.get_search_override("helmets", "custom-helmet") + + assert {:error, _} = Override.get_search_override("helmets", "custom-helmet", []) + assert {:error, _} = Override.get_search_override("helmets", "custom-helmet", conn: conn) + assert {:error, _} = Override.get_search_override("helmets", "custom-helmet", conn: map_conn) + end + + @tag ["29.0": true] + test "error: retrieve an override (> v28.0)", %{conn: conn, map_conn: map_conn} do assert {:error, %ApiResponse{message: "Collection not found"}} = Override.get_search_override("helmets", "custom-helmet") diff --git a/test/operations/presets_test.exs b/test/operations/presets_test.exs index ea8aa0e..964fa15 100644 --- a/test/operations/presets_test.exs +++ b/test/operations/presets_test.exs @@ -33,7 +33,7 @@ defmodule PresetsTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list presets", %{conn: conn, map_conn: map_conn} do assert {:ok, %PresetsRetrieveSchema{presets: presets}} = Presets.retrieve_all_presets() assert length(presets) >= 1 @@ -43,7 +43,7 @@ defmodule PresetsTest do assert {:ok, _} = Presets.retrieve_all_presets(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: get a preset", %{conn: conn, map_conn: map_conn} do assert {:error, %ApiResponse{message: "Not found."}} = Presets.retrieve_preset("listing_view") assert {:error, _} = Presets.retrieve_preset("listing_view", []) @@ -51,7 +51,7 @@ defmodule PresetsTest do assert {:error, _} = Presets.retrieve_preset("listing_view", conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: upsert a preset", %{conn: conn, map_conn: map_conn} do body = %{ @@ -70,7 +70,7 @@ defmodule PresetsTest do assert {:ok, _} = Presets.upsert_preset("restaurant_view", body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete a preset", %{conn: conn, map_conn: map_conn} do body = %{ diff --git a/test/operations/stopwords_test.exs b/test/operations/stopwords_test.exs index 4114cf7..8026c27 100644 --- a/test/operations/stopwords_test.exs +++ b/test/operations/stopwords_test.exs @@ -24,7 +24,7 @@ defmodule StopwordsTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list stopwords sets", %{conn: conn, map_conn: map_conn} do assert {:ok, %StopwordsSetsRetrieveAllSchema{stopwords: stopwords}} = Stopwords.retrieve_stopwords_sets() @@ -35,7 +35,7 @@ defmodule StopwordsTest do assert {:ok, _} = Stopwords.retrieve_stopwords_sets(conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: add stopwords", %{conn: conn, map_conn: map_conn} do set_id = "stopword_set_countries" @@ -51,7 +51,7 @@ defmodule StopwordsTest do assert {:ok, _} = Stopwords.upsert_stopwords_set(set_id, body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: retrieve specific stopwords set", %{conn: conn, map_conn: map_conn} do set_id = "stopword_set_names" @@ -71,7 +71,7 @@ defmodule StopwordsTest do assert {:ok, _} = Stopwords.retrieve_stopwords_set(set_id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete specific stopwords set", %{conn: conn, map_conn: map_conn} do set_id = "stopword_set_companies" @@ -88,7 +88,7 @@ defmodule StopwordsTest do assert {:error, _} = Stopwords.delete_stopwords_set(set_id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "field" do assert [id: {:string, :generic}] = Stopwords.__fields__(:delete_stopwords_set_200_json_resp) end diff --git a/test/operations/synonyms_test.exs b/test/operations/synonyms_test.exs index 8d519ac..9dc7459 100644 --- a/test/operations/synonyms_test.exs +++ b/test/operations/synonyms_test.exs @@ -32,7 +32,7 @@ defmodule SynonymsTest do %{coll_name: collection_name, conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: list collection synonyms", %{ coll_name: coll_name, conn: conn, @@ -48,7 +48,7 @@ defmodule SynonymsTest do assert {:ok, _} = Synonyms.get_search_synonyms(coll_name, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: upsert a collection synonym", %{ coll_name: coll_name, conn: conn, @@ -70,7 +70,7 @@ defmodule SynonymsTest do assert {:ok, _} = Synonyms.upsert_search_synonym(coll_name, synonym_id, body, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: delete a collection synonym", %{ coll_name: coll_name, conn: conn, @@ -93,7 +93,7 @@ defmodule SynonymsTest do assert {:error, _} = Synonyms.delete_search_synonym(coll_name, synonym_id, conn: map_conn) end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "success: get a collection synonym", %{ coll_name: coll_name, conn: conn, From d8293712f0a777fa5051015650a47e460329f236 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Sat, 13 Dec 2025 23:57:30 +0800 Subject: [PATCH 4/6] wip --- docker-compose.yml | 2 +- guides/custom_http_client.md | 2 +- test/operations/analytics_test.exs | 2 +- test/operations/collections_test.exs | 25 +++++++++++++++++++++++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b922d20..2c6f9a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: typesense: - image: docker.io/typesense/typesense:29.0 + image: docker.io/typesense/typesense:26.0 container_name: typesense restart: on-failure ports: diff --git a/guides/custom_http_client.md b/guides/custom_http_client.md index 078d26f..3342aa9 100644 --- a/guides/custom_http_client.md +++ b/guides/custom_http_client.md @@ -16,7 +16,7 @@ config :open_api_typesense, ## [`:httpc`](https://www.erlang.org/doc/apps/inets/httpc.html) ```elixir -defmodule CustomClient do +defmodule MyApp.CustomClient do @behaviour OpenApiTypesense.Client @impl OpenApiTypesense.Client diff --git a/test/operations/analytics_test.exs b/test/operations/analytics_test.exs index 6671efe..387e9e0 100644 --- a/test/operations/analytics_test.exs +++ b/test/operations/analytics_test.exs @@ -79,7 +79,7 @@ defmodule AnalyticsTest do %{conn: conn, map_conn: map_conn} end - @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: create analytics rule with non-existent collection", %{ conn: conn, map_conn: map_conn diff --git a/test/operations/collections_test.exs b/test/operations/collections_test.exs index d231b22..d55e614 100644 --- a/test/operations/collections_test.exs +++ b/test/operations/collections_test.exs @@ -168,8 +168,18 @@ defmodule CollectionsTest do Collections.delete_alias(alias_name, conn: map_conn) end - @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existing alias", %{conn: conn, map_conn: map_conn} do + assert Collections.get_alias("non-existing-alias") == + {:error, %ApiResponse{message: "Not Found"}} + + assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", []) + assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", conn: conn) + assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", conn: map_conn) + end + + @tag ["29.0": true] + test "error: get a non-existing alias (> v28.0)", %{conn: conn, map_conn: map_conn} do assert Collections.get_alias("non-existing-alias") == {:error, %ApiResponse{message: "Collection not found"}} @@ -178,8 +188,19 @@ defmodule CollectionsTest do assert {:error, %ApiResponse{message: _}} = Collections.get_alias("xyz", conn: map_conn) end - @tag ["29.0": true, "28.0": true, "27.1": true, "27.0": true, "26.0": true] + @tag ["28.0": true, "27.1": true, "27.0": true, "26.0": true] test "error: get a non-existing collection", %{conn: conn, map_conn: map_conn} do + assert Collections.get_collection("non-existing-collection") == + {:error, %ApiResponse{message: "Not Found"}} + + assert {:error, %ApiResponse{message: _}} = Collections.get_collection("xyz") + assert {:error, %ApiResponse{message: _}} = Collections.get_collection("xyz", []) + assert {:error, %ApiResponse{message: _}} = Collections.get_collection("xyz", conn: conn) + assert {:error, %ApiResponse{message: _}} = Collections.get_collection("xyz", conn: map_conn) + end + + @tag ["29.0": true] + test "error: get a non-existing collection (> v28.0)", %{conn: conn, map_conn: map_conn} do assert Collections.get_collection("non-existing-collection") == {:error, %ApiResponse{message: "Collection not found"}} From f6c915ef2ce2fea3fefe0649064399c788e53bde Mon Sep 17 00:00:00 2001 From: jaeyson Date: Mon, 15 Dec 2025 10:30:10 +0800 Subject: [PATCH 5/6] wip ci --- .github/workflows/ci_v29.0.yml | 1 + docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_v29.0.yml b/.github/workflows/ci_v29.0.yml index 3ad8ae6..d55b9d3 100644 --- a/.github/workflows/ci_v29.0.yml +++ b/.github/workflows/ci_v29.0.yml @@ -1,6 +1,7 @@ name: CI v29.0 on: + workflow_call: pull_request: branches: ["main"] diff --git a/docker-compose.yml b/docker-compose.yml index 2c6f9a9..b922d20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: typesense: - image: docker.io/typesense/typesense:26.0 + image: docker.io/typesense/typesense:29.0 container_name: typesense restart: on-failure ports: From d7645d2a8033d6717c3a90d759f909450e42df7e Mon Sep 17 00:00:00 2001 From: jaeyson Date: Mon, 15 Dec 2025 10:31:21 +0800 Subject: [PATCH 6/6] remove deadlock --- .github/workflows/llm.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/llm.yml b/.github/workflows/llm.yml index 32379b2..98307f6 100644 --- a/.github/workflows/llm.yml +++ b/.github/workflows/llm.yml @@ -3,9 +3,9 @@ on: pull_request: branches: ["main"] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +# concurrency: +# group: ${{ github.workflow }}-${{ github.ref }} +# cancel-in-progress: true jobs: ci_workflow: