Add OpenAPI spec for SearchController
authorEgor Kislitsyn <egor@kislitsyn.com>
Thu, 7 May 2020 10:04:48 +0000 (14:04 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Thu, 7 May 2020 10:04:48 +0000 (14:04 +0400)
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/api_spec/operations/search_operation.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/controllers/search_controller.ex
test/web/mastodon_api/controllers/search_controller_test.exs

index 470fc0215f63a3a0fce3634a9419068e32eeed61..70069d6f9671307ec3f07e44b5a8523dedd3fa26 100644 (file)
@@ -556,11 +556,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  defp array_of_accounts do
+  def array_of_accounts do
     %Schema{
       title: "ArrayOfAccounts",
       type: :array,
-      items: Account
+      items: Account,
+      example: [Account.schema().example]
     }
   end
 
diff --git a/lib/pleroma/web/api_spec/operations/search_operation.ex b/lib/pleroma/web/api_spec/operations/search_operation.ex
new file mode 100644 (file)
index 0000000..ec1ae5d
--- /dev/null
@@ -0,0 +1,207 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.SearchOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.AccountOperation
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.Status
+  alias Pleroma.Web.ApiSpec.Schemas.Tag
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def account_search_operation do
+    %Operation{
+      tags: ["Search"],
+      summary: "Search for matching accounts by username or display name",
+      operationId: "SearchController.account_search",
+      parameters: [
+        Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for",
+          required: true
+        ),
+        Operation.parameter(
+          :limit,
+          :query,
+          %Schema{type: :integer, default: 40},
+          "Maximum number of results"
+        ),
+        Operation.parameter(
+          :resolve,
+          :query,
+          %Schema{allOf: [BooleanLike], default: false},
+          "Attempt WebFinger lookup. Use this when `q` is an exact address."
+        ),
+        Operation.parameter(
+          :following,
+          :query,
+          %Schema{allOf: [BooleanLike], default: false},
+          "Only who the user is following."
+        )
+      ],
+      responses: %{
+        200 =>
+          Operation.response(
+            "Array of Account",
+            "application/json",
+            AccountOperation.array_of_accounts()
+          )
+      }
+    }
+  end
+
+  def search_operation do
+    %Operation{
+      tags: ["Search"],
+      summary: "Search results",
+      security: [%{"oAuth" => ["read:search"]}],
+      operationId: "SearchController.search",
+      deprecated: true,
+      parameters: [
+        Operation.parameter(
+          :account_id,
+          :query,
+          FlakeID,
+          "If provided, statuses returned will be authored only by this account"
+        ),
+        Operation.parameter(
+          :type,
+          :query,
+          %Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]},
+          "Search type"
+        ),
+        Operation.parameter(:q, :query, %Schema{type: :string}, "The search query", required: true),
+        Operation.parameter(
+          :resolve,
+          :query,
+          %Schema{allOf: [BooleanLike], default: false},
+          "Attempt WebFinger lookup"
+        ),
+        Operation.parameter(
+          :following,
+          :query,
+          %Schema{allOf: [BooleanLike], default: false},
+          "Only include accounts that the user is following"
+        ),
+        Operation.parameter(
+          :offset,
+          :query,
+          %Schema{type: :integer},
+          "Offset"
+        )
+        | pagination_params()
+      ],
+      responses: %{
+        200 => Operation.response("Results", "application/json", results())
+      }
+    }
+  end
+
+  def search2_operation do
+    %Operation{
+      tags: ["Search"],
+      summary: "Search results",
+      security: [%{"oAuth" => ["read:search"]}],
+      operationId: "SearchController.search2",
+      parameters: [
+        Operation.parameter(
+          :account_id,
+          :query,
+          FlakeID,
+          "If provided, statuses returned will be authored only by this account"
+        ),
+        Operation.parameter(
+          :type,
+          :query,
+          %Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]},
+          "Search type"
+        ),
+        Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for",
+          required: true
+        ),
+        Operation.parameter(
+          :resolve,
+          :query,
+          %Schema{allOf: [BooleanLike], default: false},
+          "Attempt WebFinger lookup"
+        ),
+        Operation.parameter(
+          :following,
+          :query,
+          %Schema{allOf: [BooleanLike], default: false},
+          "Only include accounts that the user is following"
+        )
+        | pagination_params()
+      ],
+      responses: %{
+        200 => Operation.response("Results", "application/json", results2())
+      }
+    }
+  end
+
+  defp results2 do
+    %Schema{
+      title: "SearchResults",
+      type: :object,
+      properties: %{
+        accounts: %Schema{
+          type: :array,
+          items: Account,
+          description: "Accounts which match the given query"
+        },
+        statuses: %Schema{
+          type: :array,
+          items: Status,
+          description: "Statuses which match the given query"
+        },
+        hashtags: %Schema{
+          type: :array,
+          items: Tag,
+          description: "Hashtags which match the given query"
+        }
+      },
+      example: %{
+        "accounts" => [Account.schema().example],
+        "statuses" => [Status.schema().example],
+        "hashtags" => [Tag.schema().example]
+      }
+    }
+  end
+
+  defp results do
+    %Schema{
+      title: "SearchResults",
+      type: :object,
+      properties: %{
+        accounts: %Schema{
+          type: :array,
+          items: Account,
+          description: "Accounts which match the given query"
+        },
+        statuses: %Schema{
+          type: :array,
+          items: Status,
+          description: "Statuses which match the given query"
+        },
+        hashtags: %Schema{
+          type: :array,
+          items: %Schema{type: :string},
+          description: "Hashtags which match the given query"
+        }
+      },
+      example: %{
+        "accounts" => [Account.schema().example],
+        "statuses" => [Status.schema().example],
+        "hashtags" => ["cofe"]
+      }
+    }
+  end
+end
index cd49da6ad5e236ddfb9ce072e55328e2f6423d83..0e0d54ba41891a9cfe400e2afdcf9c2f35ead2eb 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.MastodonAPI.SearchController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
+  import Pleroma.Web.ControllerHelper, only: [skip_relationships?: 1]
 
   alias Pleroma.Activity
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -18,6 +18,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
 
   require Logger
 
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
   # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
   plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated})
 
@@ -25,7 +27,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
 
   plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search])
 
-  def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation
+
+  def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do
     accounts = User.search(query, search_options(params, user))
 
     conn
@@ -36,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   def search2(conn, params), do: do_search(:v2, conn, params)
   def search(conn, params), do: do_search(:v1, conn, params)
 
-  defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+  defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
     options = search_options(params, user)
     timeout = Keyword.get(Repo.config(), :timeout, 15_000)
     default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
@@ -44,7 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
     result =
       default_values
       |> Enum.map(fn {resource, default_value} ->
-        if params["type"] in [nil, resource] do
+        if params[:type] in [nil, resource] do
           {resource, fn -> resource_search(version, resource, query, options) end}
         else
           {resource, fn -> default_value end}
@@ -68,11 +72,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   defp search_options(params, user) do
     [
       skip_relationships: skip_relationships?(params),
-      resolve: params["resolve"] == "true",
-      following: params["following"] == "true",
-      limit: fetch_integer_param(params, "limit"),
-      offset: fetch_integer_param(params, "offset"),
-      type: params["type"],
+      resolve: params[:resolve],
+      following: params[:following],
+      limit: params[:limit],
+      offset: params[:offset],
+      type: params[:type],
       author: get_author(params),
       for_user: user
     ]
@@ -135,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
     end
   end
 
-  defp get_author(%{"account_id" => account_id}) when is_binary(account_id),
+  defp get_author(%{account_id: account_id}) when is_binary(account_id),
     do: User.get_cached_by_id(account_id)
 
   defp get_author(_params), do: nil
index 11133ff66becbdf2d2f7c7fea660ee128677c266..02476acb60888084e98484118d91673f8bc9afb5 100644 (file)
@@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         capture_log(fn ->
           results =
             conn
-            |> get("/api/v2/search", %{"q" => "2hu"})
-            |> json_response(200)
+            |> get("/api/v2/search?q=2hu")
+            |> json_response_and_validate_schema(200)
 
           assert results["accounts"] == []
           assert results["statuses"] == []
@@ -54,8 +54,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v2/search", %{"q" => "2hu #private"})
-        |> json_response(200)
+        |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu #private"})}")
+        |> json_response_and_validate_schema(200)
 
       [account | _] = results["accounts"]
       assert account["id"] == to_string(user_three.id)
@@ -68,8 +68,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
       assert status["id"] == to_string(activity.id)
 
       results =
-        get(conn, "/api/v2/search", %{"q" => "天子"})
-        |> json_response(200)
+        get(conn, "/api/v2/search?q=天子")
+        |> json_response_and_validate_schema(200)
 
       [status] = results["statuses"]
       assert status["id"] == to_string(activity.id)
@@ -89,8 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         conn
         |> assign(:user, user)
         |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
-        |> get("/api/v2/search", %{"q" => "Agent"})
-        |> json_response(200)
+        |> get("/api/v2/search?q=Agent")
+        |> json_response_and_validate_schema(200)
 
       status_ids = Enum.map(results["statuses"], fn g -> g["id"] end)
 
@@ -107,8 +107,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v1/accounts/search", %{"q" => "shp"})
-        |> json_response(200)
+        |> get("/api/v1/accounts/search?q=shp")
+        |> json_response_and_validate_schema(200)
 
       result_ids = for result <- results, do: result["acct"]
 
@@ -117,8 +117,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v1/accounts/search", %{"q" => "2hu"})
-        |> json_response(200)
+        |> get("/api/v1/accounts/search?q=2hu")
+        |> json_response_and_validate_schema(200)
 
       result_ids = for result <- results, do: result["acct"]
 
@@ -130,8 +130,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "})
-        |> json_response(200)
+        |> get("/api/v1/accounts/search?q=shp@shitposter.club xxx")
+        |> json_response_and_validate_schema(200)
 
       assert length(results) == 1
     end
@@ -146,8 +146,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         capture_log(fn ->
           results =
             conn
-            |> get("/api/v1/search", %{"q" => "2hu"})
-            |> json_response(200)
+            |> get("/api/v1/search?q=2hu")
+            |> json_response_and_validate_schema(200)
 
           assert results["accounts"] == []
           assert results["statuses"] == []
@@ -173,8 +173,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v1/search", %{"q" => "2hu"})
-        |> json_response(200)
+        |> get("/api/v1/search?q=2hu")
+        |> json_response_and_validate_schema(200)
 
       [account | _] = results["accounts"]
       assert account["id"] == to_string(user_three.id)
@@ -194,8 +194,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
         results =
           conn
-          |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
-          |> json_response(200)
+          |> get("/api/v1/search?q=https://shitposter.club/notice/2827873")
+          |> json_response_and_validate_schema(200)
 
         [status, %{"id" => ^activity_id}] = results["statuses"]
 
@@ -212,10 +212,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         })
 
       capture_log(fn ->
+        q = Object.normalize(activity).data["id"]
+
         results =
           conn
-          |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
-          |> json_response(200)
+          |> get("/api/v1/search?q=#{q}")
+          |> json_response_and_validate_schema(200)
 
         [] = results["statuses"]
       end)
@@ -228,8 +230,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         conn
         |> assign(:user, user)
         |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
-        |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"})
-        |> json_response(200)
+        |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=true")
+        |> json_response_and_validate_schema(200)
 
       [account] = results["accounts"]
       assert account["acct"] == "mike@osada.macgirvin.com"
@@ -238,8 +240,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
     test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
       results =
         conn
-        |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"})
-        |> json_response(200)
+        |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false")
+        |> json_response_and_validate_schema(200)
 
       assert [] == results["accounts"]
     end
@@ -254,16 +256,16 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       result =
         conn
-        |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1})
+        |> get("/api/v1/search?q=2hu&limit=1")
 
-      assert results = json_response(result, 200)
+      assert results = json_response_and_validate_schema(result, 200)
       assert [%{"id" => activity_id1}] = results["statuses"]
       assert [_] = results["accounts"]
 
       results =
         conn
-        |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1, "offset" => 1})
-        |> json_response(200)
+        |> get("/api/v1/search?q=2hu&limit=1&offset=1")
+        |> json_response_and_validate_schema(200)
 
       assert [%{"id" => activity_id2}] = results["statuses"]
       assert [] = results["accounts"]
@@ -279,13 +281,13 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} =
                conn
-               |> get("/api/v1/search", %{"q" => "2hu", "type" => "statuses"})
-               |> json_response(200)
+               |> get("/api/v1/search?q=2hu&type=statuses")
+               |> json_response_and_validate_schema(200)
 
       assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} =
                conn
-               |> get("/api/v1/search", %{"q" => "2hu", "type" => "accounts"})
-               |> json_response(200)
+               |> get("/api/v1/search?q=2hu&type=accounts")
+               |> json_response_and_validate_schema(200)
     end
 
     test "search uses account_id to filter statuses by the author", %{conn: conn} do
@@ -297,8 +299,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user.id})
-        |> json_response(200)
+        |> get("/api/v1/search?q=2hu&account_id=#{user.id}")
+        |> json_response_and_validate_schema(200)
 
       assert [%{"id" => activity_id1}] = results["statuses"]
       assert activity_id1 == activity1.id
@@ -306,8 +308,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
 
       results =
         conn
-        |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user_two.id})
-        |> json_response(200)
+        |> get("/api/v1/search?q=2hu&account_id=#{user_two.id}")
+        |> json_response_and_validate_schema(200)
 
       assert [%{"id" => activity_id2}] = results["statuses"]
       assert activity_id2 == activity2.id