Force spec for every operation to have a listed tag
This commit is contained in:
parent
bddcb3ed68
commit
3b4b84b74c
12 changed files with 172 additions and 32 deletions
|
@ -6,7 +6,70 @@ defmodule Mix.Tasks.Pleroma.OpenapiSpec do
|
|||
def run([path]) do
|
||||
# Load Pleroma application to get version info
|
||||
Application.load(:pleroma)
|
||||
spec = Pleroma.Web.ApiSpec.spec(server_specific: false) |> Jason.encode!()
|
||||
File.write(path, spec)
|
||||
|
||||
spec_json = Pleroma.Web.ApiSpec.spec(server_specific: false) |> Jason.encode!()
|
||||
# to get rid of the structs
|
||||
spec_regened = spec_json |> Jason.decode!()
|
||||
|
||||
check_specs!(spec_regened)
|
||||
|
||||
File.write(path, spec_json)
|
||||
end
|
||||
|
||||
defp check_specs!(spec) do
|
||||
with :ok <- check_specs(spec) do
|
||||
:ok
|
||||
else
|
||||
{_, errors} ->
|
||||
IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"]))
|
||||
Enum.map(errors, &IO.puts/1)
|
||||
|
||||
raise "Spec check failed"
|
||||
end
|
||||
end
|
||||
|
||||
def check_specs(spec) do
|
||||
errors =
|
||||
spec["paths"]
|
||||
|> Enum.flat_map(fn {path, %{} = endpoints} ->
|
||||
Enum.map(
|
||||
endpoints,
|
||||
fn {method, endpoint} ->
|
||||
with :ok <- check_endpoint(spec, endpoint) do
|
||||
:ok
|
||||
else
|
||||
error ->
|
||||
"#{endpoint["operationId"]} (#{method} #{path}): #{error}"
|
||||
end
|
||||
end
|
||||
)
|
||||
|> Enum.reject(fn res -> res == :ok end)
|
||||
end)
|
||||
|
||||
if errors == [] do
|
||||
:ok
|
||||
else
|
||||
{:error, errors}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_endpoint(spec, endpoint) do
|
||||
valid_tags = available_tags(spec)
|
||||
|
||||
with {_, [_ | _] = tags} <- {:tags, endpoint["tags"]},
|
||||
{_, []} <- {:unavailable, Enum.reject(tags, &(&1 in valid_tags))} do
|
||||
:ok
|
||||
else
|
||||
{:tags, _} ->
|
||||
"No tags specified"
|
||||
|
||||
{:unavailable, tags} ->
|
||||
"Tags #{inspect(tags)} not available. Please add it in \"x-tagGroups\" in Pleroma.Web.ApiSpec"
|
||||
end
|
||||
end
|
||||
|
||||
defp available_tags(spec) do
|
||||
spec["x-tagGroups"]
|
||||
|> Enum.flat_map(fn %{"tags" => tags} -> tags end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -95,7 +95,8 @@ def spec(opts \\ []) do
|
|||
"Relays",
|
||||
"Report managment",
|
||||
"Status administration",
|
||||
"User administration"
|
||||
"User administration",
|
||||
"Announcement management"
|
||||
]
|
||||
},
|
||||
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
|
||||
|
@ -110,10 +111,12 @@ def spec(opts \\ []) do
|
|||
"Follow requests",
|
||||
"Mascot",
|
||||
"Markers",
|
||||
"Notifications"
|
||||
"Notifications",
|
||||
"Filters",
|
||||
"Settings"
|
||||
]
|
||||
},
|
||||
%{"name" => "Instance", "tags" => ["Custom emojis"]},
|
||||
%{"name" => "Instance", "tags" => ["Custom emojis", "Instance misc"]},
|
||||
%{"name" => "Messaging", "tags" => ["Chats", "Conversations"]},
|
||||
%{
|
||||
"name" => "Statuses",
|
||||
|
@ -125,10 +128,21 @@ def spec(opts \\ []) do
|
|||
"Retrieve status information",
|
||||
"Scheduled statuses",
|
||||
"Search",
|
||||
"Status actions"
|
||||
"Status actions",
|
||||
"Media attachments"
|
||||
]
|
||||
},
|
||||
%{"name" => "Miscellaneous", "tags" => ["Emoji packs", "Reports", "Suggestions"]}
|
||||
%{
|
||||
"name" => "Miscellaneous",
|
||||
"tags" => [
|
||||
"Emoji packs",
|
||||
"Reports",
|
||||
"Suggestions",
|
||||
"Announcements",
|
||||
"Remote interaction",
|
||||
"Others"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -461,7 +461,7 @@ def blocks_operation do
|
|||
|
||||
def lookup_operation do
|
||||
%Operation{
|
||||
tags: ["Account lookup"],
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Find a user by nickname",
|
||||
operationId: "AccountController.lookup",
|
||||
parameters: [
|
||||
|
|
|
@ -17,7 +17,7 @@ def open_api_operation(action) do
|
|||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement managment"],
|
||||
tags: ["Announcement management"],
|
||||
summary: "Retrieve a list of announcements",
|
||||
operationId: "AdminAPI.AnnouncementController.index",
|
||||
security: [%{"oAuth" => ["admin:read"]}],
|
||||
|
@ -46,7 +46,7 @@ def index_operation do
|
|||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement managment"],
|
||||
tags: ["Announcement management"],
|
||||
summary: "Display one announcement",
|
||||
operationId: "AdminAPI.AnnouncementController.show",
|
||||
security: [%{"oAuth" => ["admin:read"]}],
|
||||
|
@ -69,7 +69,7 @@ def show_operation do
|
|||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement managment"],
|
||||
tags: ["Announcement management"],
|
||||
summary: "Delete one announcement",
|
||||
operationId: "AdminAPI.AnnouncementController.delete",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
|
@ -92,7 +92,7 @@ def delete_operation do
|
|||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement managment"],
|
||||
tags: ["Announcement management"],
|
||||
summary: "Create one announcement",
|
||||
operationId: "AdminAPI.AnnouncementController.create",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
|
@ -107,7 +107,7 @@ def create_operation do
|
|||
|
||||
def change_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement managment"],
|
||||
tags: ["Announcement management"],
|
||||
summary: "Change one announcement",
|
||||
operationId: "AdminAPI.AnnouncementController.change",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
|
|
|
@ -70,7 +70,7 @@ def index_operation do
|
|||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Status adminitration)"],
|
||||
tags: ["Status administration"],
|
||||
summary: "Get status",
|
||||
operationId: "AdminAPI.StatusController.show",
|
||||
parameters: [id_param() | admin_api_params()],
|
||||
|
@ -84,7 +84,7 @@ def show_operation do
|
|||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Status adminitration)"],
|
||||
tags: ["Status administration"],
|
||||
summary: "Change the scope of a status",
|
||||
operationId: "AdminAPI.StatusController.update",
|
||||
parameters: [id_param() | admin_api_params()],
|
||||
|
@ -99,7 +99,7 @@ def update_operation do
|
|||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Status adminitration)"],
|
||||
tags: ["Status administration"],
|
||||
summary: "Delete status",
|
||||
operationId: "AdminAPI.StatusController.delete",
|
||||
parameters: [id_param() | admin_api_params()],
|
||||
|
|
|
@ -15,7 +15,7 @@ def open_api_operation(action) do
|
|||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement"],
|
||||
tags: ["Announcements"],
|
||||
summary: "Retrieve a list of announcements",
|
||||
operationId: "MastodonAPI.AnnouncementController.index",
|
||||
security: [%{"oAuth" => []}],
|
||||
|
@ -28,7 +28,7 @@ def index_operation do
|
|||
|
||||
def mark_read_operation do
|
||||
%Operation{
|
||||
tags: ["Announcement"],
|
||||
tags: ["Announcements"],
|
||||
summary: "Mark one announcement as read",
|
||||
operationId: "MastodonAPI.AnnouncementController.mark_read",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
|
|
|
@ -17,7 +17,7 @@ def open_api_operation(action) do
|
|||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Directory"],
|
||||
tags: ["Others"],
|
||||
summary: "Profile directory",
|
||||
operationId: "DirectoryController.index",
|
||||
parameters:
|
||||
|
|
|
@ -13,7 +13,7 @@ def open_api_operation(action) do
|
|||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Instance"],
|
||||
tags: ["Instance misc"],
|
||||
summary: "Retrieve instance information",
|
||||
description: "Information about the server",
|
||||
operationId: "InstanceController.show",
|
||||
|
@ -25,7 +25,7 @@ def show_operation do
|
|||
|
||||
def peers_operation do
|
||||
%Operation{
|
||||
tags: ["Instance"],
|
||||
tags: ["Instance misc"],
|
||||
summary: "Retrieve list of known instances",
|
||||
operationId: "InstanceController.peers",
|
||||
responses: %{
|
||||
|
|
|
@ -13,7 +13,7 @@ def open_api_operation(action) do
|
|||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Instance"],
|
||||
tags: ["Instance misc"],
|
||||
summary: "Retrieve federation status",
|
||||
description: "Information about instances deemed unreachable by the server",
|
||||
operationId: "PleromaInstances.show",
|
||||
|
|
|
@ -440,7 +440,7 @@ def bookmarks_operation do
|
|||
|
||||
def show_history_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve status history"],
|
||||
tags: ["Retrieve status information"],
|
||||
summary: "Status history",
|
||||
description: "View history of a status",
|
||||
operationId: "StatusController.show_history",
|
||||
|
@ -457,7 +457,7 @@ def show_history_operation do
|
|||
|
||||
def show_source_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve status source"],
|
||||
tags: ["Retrieve status information"],
|
||||
summary: "Status source",
|
||||
description: "View source of a status",
|
||||
operationId: "StatusController.show_source",
|
||||
|
@ -474,7 +474,7 @@ def show_source_operation do
|
|||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Update status"],
|
||||
tags: ["Status actions"],
|
||||
summary: "Update status",
|
||||
description: "Change the content of a status",
|
||||
operationId: "StatusController.update",
|
||||
|
|
|
@ -17,7 +17,7 @@ def open_api_operation(action) do
|
|||
|
||||
def emoji_operation do
|
||||
%Operation{
|
||||
tags: ["Emojis"],
|
||||
tags: ["Custom emojis"],
|
||||
summary: "List all custom emojis",
|
||||
operationId: "UtilController.emoji",
|
||||
parameters: [],
|
||||
|
@ -46,7 +46,7 @@ def emoji_operation do
|
|||
|
||||
def frontend_configurations_operation do
|
||||
%Operation{
|
||||
tags: ["Configuration"],
|
||||
tags: ["Others"],
|
||||
summary: "Dump frontend configurations",
|
||||
operationId: "UtilController.frontend_configurations",
|
||||
parameters: [],
|
||||
|
@ -138,7 +138,7 @@ defp change_email_request do
|
|||
|
||||
def update_notificaton_settings_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
tags: ["Settings"],
|
||||
summary: "Update Notification Settings",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.update_notificaton_settings",
|
||||
|
@ -213,6 +213,7 @@ def captcha_operation do
|
|||
%Operation{
|
||||
summary: "Get a captcha",
|
||||
operationId: "UtilController.captcha",
|
||||
tags: ["Others"],
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 => Operation.response("Success", "application/json", %Schema{type: :object})
|
||||
|
@ -362,7 +363,7 @@ defp delete_alias_request do
|
|||
|
||||
def healthcheck_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
tags: ["Others"],
|
||||
summary: "Quick status check on the instance",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
operationId: "UtilController.healthcheck",
|
||||
|
@ -377,7 +378,7 @@ def healthcheck_operation do
|
|||
|
||||
def remote_subscribe_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
tags: ["Remote interaction"],
|
||||
summary: "Remote Subscribe",
|
||||
operationId: "UtilController.remote_subscribe",
|
||||
parameters: [],
|
||||
|
@ -387,7 +388,7 @@ def remote_subscribe_operation do
|
|||
|
||||
def remote_interaction_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
tags: ["Remote interaction"],
|
||||
summary: "Remote interaction",
|
||||
operationId: "UtilController.remote_interaction",
|
||||
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
|
||||
|
@ -413,7 +414,7 @@ defp remote_interaction_request do
|
|||
|
||||
def show_subscribe_form_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
tags: ["Remote interaction"],
|
||||
summary: "Show remote subscribe form",
|
||||
operationId: "UtilController.show_subscribe_form",
|
||||
parameters: [],
|
||||
|
|
62
test/mix/tasks/pleroma/openapi_spec_test.exs
Normal file
62
test/mix/tasks/pleroma/openapi_spec_test.exs
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.OpenapiSpecTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
alias Mix.Tasks.Pleroma.OpenapiSpec
|
||||
|
||||
@spec_base %{
|
||||
"paths" => %{
|
||||
"/cofe" => %{
|
||||
"get" => %{
|
||||
"operationId" => "Some.operation",
|
||||
"tags" => []
|
||||
}
|
||||
},
|
||||
"/mew" => %{
|
||||
"post" => %{
|
||||
"operationId" => "Another.operation",
|
||||
"tags" => ["mew mew"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-tagGroups" => [
|
||||
%{
|
||||
"name" => "mew",
|
||||
"tags" => ["mew mew", "abc"]
|
||||
},
|
||||
%{
|
||||
"name" => "lol",
|
||||
"tags" => ["lol lol", "xyz"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
describe "check_specs/1" do
|
||||
test "Every operation must have a tag" do
|
||||
assert {:error, ["Some.operation (get /cofe): No tags specified"]} ==
|
||||
OpenapiSpec.check_specs(@spec_base)
|
||||
end
|
||||
|
||||
test "Every tag must be in tag groups" do
|
||||
spec =
|
||||
@spec_base
|
||||
|> put_in(["paths", "/cofe", "get", "tags"], ["abc", "def", "not specified"])
|
||||
|
||||
assert {:error,
|
||||
[
|
||||
"Some.operation (get /cofe): Tags #{inspect(["def", "not specified"])} not available. Please add it in \"x-tagGroups\" in Pleroma.Web.ApiSpec"
|
||||
]} == OpenapiSpec.check_specs(spec)
|
||||
end
|
||||
|
||||
test "No errors if ok" do
|
||||
spec =
|
||||
@spec_base
|
||||
|> put_in(["paths", "/cofe", "get", "tags"], ["abc", "mew mew"])
|
||||
|
||||
assert :ok == OpenapiSpec.check_specs(spec)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue