Skip to content

Commit

Permalink
Merge pull request #57 from brainn-co/fix#28/handle-structs-as-params
Browse files Browse the repository at this point in the history
Fix#28/handle structs as params
  • Loading branch information
Danielwsx64 authored Nov 30, 2020
2 parents a6eb87d + 4013cfe commit 5bd5d19
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 16 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.7.7] - 2020-11-27

### Fixed

- Validate requests and report an error message when found structs in HTTP params

## [0.7.6] - 2020-11-14

### Fixed
Expand Down Expand Up @@ -101,7 +107,8 @@ Improve CI/CD flow:
- New "tags" parameter to operations object in Swagger format.
- Add changelog and Makefile.

[unreleased]: https://github.com/brainn-co/xcribe/compare/v0.7.6...master
[unreleased]: https://github.com/brainn-co/xcribe/compare/v0.7.7...master
[0.7.7]: https://github.com/brainn-co/xcribe/compare/v0.7.6...v0.7.7
[0.7.6]: https://github.com/brainn-co/xcribe/compare/v0.7.5...v0.7.6
[0.7.5]: https://github.com/brainn-co/xcribe/compare/v0.7.4...v0.7.5
[0.7.4]: https://github.com/brainn-co/xcribe/compare/v0.7.3...v0.7.4
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mix.exs
```elixir
def deps do
[
{:xcribe, "~> 0.7.6"}
{:xcribe, "~> 0.7.7"}
]
end
```
Expand Down
12 changes: 8 additions & 4 deletions lib/xcribe/cli/output.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ defmodule Xcribe.CLI.Output do
alias Xcribe.DocException

def print_request_errors(errors) do
print_header_error("[ Xcribe ] Parsing Errors", @bg_blue)
print_header_error("[ Xcribe ] Parsing and validation errors", @bg_blue)

Enum.each(errors, &print_error/1)
end

def print_configuration_errors(errors) do
print_header_error("[ Xcribe ] Configuration Errors", @bg_green)
print_header_error("[ Xcribe ] Configuration errors", @bg_green)

Enum.each(errors, &print_error/1)
end
Expand Down Expand Up @@ -57,12 +57,13 @@ defmodule Xcribe.CLI.Output do
""")
end

defp print_error(%{type: :parsing, message: msg, __meta__: %{call: call}}) do
defp print_error(%{type: typ, message: msg, __meta__: %{call: call}})
when typ in [:parsing, :validation] do
line_call = get_line(call.file, call.line)

IO.puts("""
#{tab(@blue)}
#{tab(@blue)} [P] → #{@yellow} #{msg}
#{tab(@blue)} [#{error_char(typ)}] → #{@yellow} #{msg}
#{tab(@blue)} #{space(6)} #{@blue}> #{call.description}
#{tab(@blue)} #{space(6)} #{@gray}#{format_file_path(call.file)}:#{call.line}
#{tab(@dark_blue)}
Expand Down Expand Up @@ -110,6 +111,9 @@ defmodule Xcribe.CLI.Output do
defp space_for(message), do: String.duplicate(" ", @bar_size - String.length(message))
defp space(count), do: String.duplicate(" ", count)

defp error_char(:parsing), do: "P"
defp error_char(:validation), do: "V"

def get_line(filename, line) do
filename
|> File.stream!()
Expand Down
19 changes: 13 additions & 6 deletions lib/xcribe/formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmodule Xcribe.Formatter do
Recorder,
Request,
Request.Error,
Request.Validator,
Swagger,
Writter
}
Expand Down Expand Up @@ -75,17 +76,23 @@ defmodule Xcribe.Formatter do

defp validate_records(records) when is_list(records) do
records
|> check_errors()
|> Enum.reduce({:ok, []}, &validate_request/2)
|> handle_errors()
end

defp check_errors(records), do: Enum.reduce(records, {:ok, []}, &reduce_records/2)
defp validate_request(%Request{} = request, acc) do
request
|> Validator.validate()
|> add_result(acc)
end

defp reduce_records(%Request{} = request, {:ok, requests}), do: {:ok, [request | requests]}
defp reduce_records(%Request{}, {:error, _errs} = err), do: err
defp validate_request(%Error{} = err, {:ok, _requests}), do: {:error, [err]}
defp validate_request(%Error{} = err, {:error, errs}), do: {:error, [err | errs]}

defp reduce_records(%Error{} = err, {:ok, _requests}), do: {:error, [err]}
defp reduce_records(%Error{} = err, {:error, errs}), do: {:error, [err | errs]}
defp add_result({:error, error}, {:error, errs}), do: {:error, [error | errs]}
defp add_result({:error, error}, {:ok, _requests}), do: {:error, [error]}
defp add_result({:ok, request}, {:ok, requests}), do: {:ok, [request | requests]}
defp add_result({:ok, _request}, {:error, _errs} = errs), do: errs

defp handle_errors({:error, errs}), do: Output.print_request_errors(errs) && :error
defp handle_errors({:ok, requests}), do: requests
Expand Down
56 changes: 56 additions & 0 deletions lib/xcribe/request/validator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule Xcribe.Request.Validator do
alias Xcribe.Request
alias Xcribe.Request.Error

def validate(%Request{} = request) do
{:ok, request}
|> validate_parameters_in(:request_body)
|> validate_parameters_in(:path_params)
|> validate_parameters_in(:header_params)
|> validate_parameters_in(:query_params)
|> handle_validate(request)
end

defp validate_parameters_in({:error, _err} = error, _key), do: error

defp validate_parameters_in({:ok, request}, key) do
request
|> Map.fetch!(key)
|> find_struct()
|> handle_validate_params(request)
end

defp find_struct(%{__struct__: module}) do
%Error{
type: :validation,
message:
"The Plug.Conn params must be valid HTTP params. A struct #{sanitize_module_name(module)} was found!"
}
end

defp find_struct(%{} = map), do: Enum.reduce_while(map, :ok, &reduce_map/2)
defp find_struct(list) when is_list(list), do: Enum.reduce_while(list, :ok, &reduce_list/2)
defp find_struct(_), do: :ok

defp reduce_list(value, _acc), do: reduce_function(value)

defp reduce_map({_key, value}, _acc), do: reduce_function(value)

defp reduce_function(value) do
case find_struct(value) do
:ok -> {:cont, :ok}
%Error{} = error -> {:halt, error}
end
end

defp sanitize_module_name(module),
do: module |> Atom.to_string() |> String.replace_prefix("Elixir.", "")

defp handle_validate_params(:ok, request), do: {:ok, request}
defp handle_validate_params(%Error{} = error, _request), do: {:error, error}

defp handle_validate({:ok, _req} = success, _request), do: success

defp handle_validate({:error, error}, request),
do: {:error, %{error | __meta__: request.__meta__}}
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Xcribe.MixProject do
use Mix.Project

@version "0.7.6"
@version "0.7.7"
@description "A lib to generate API documentation from test specs"
@links %{"GitHub" => "https://github.com/brainn-co/xcribe"}

Expand Down
39 changes: 36 additions & 3 deletions test/xcribe/cli/output_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ defmodule Xcribe.CLI.OutputTest do
}

expected_output = """
\e[44m\e[37m [ Xcribe ] Parsing Errors \e[0m
\e[44m\e[37m [ Xcribe ] Parsing and validation errors \e[0m
\e[34m┃\e[0m
\e[34m┃\e[0m [P] → \e[33m route not found
\e[34m┃\e[0m \e[34m> test name\n\e[34m┃\e[0m \e[38;5;240mtest/xcribe/cli/output_test.exs:13
Expand All @@ -63,6 +63,39 @@ defmodule Xcribe.CLI.OutputTest do
assert Output.print_request_errors([route_error, conn_error]) == :ok
end) == expected_output
end

test "printing request validation error" do
validation_error = %Error{
type: :validation,
message:
"The Plug.Conn params must be valid HTTP params. A struct Elixir.Date was found!",
__meta__: %{
call: %{
description: "test name",
file: File.cwd!() <> "/test/xcribe/cli/output_test.exs",
line: 13
}
}
}

expected_output = """
\e[44m\e[37m [ Xcribe ] Parsing and validation errors \e[0m
\e[34m┃\e[0m
\e[34m┃\e[0m [V] → \e[33m The Plug.Conn params must be valid HTTP params. A struct Elixir.Date was found!
\e[34m┃\e[0m \e[34m> test name
\e[34m┃\e[0m \e[38;5;240mtest/xcribe/cli/output_test.exs:13
\e[38;5;25m┃\e[0m
\e[38;5;25m┃\e[0m \e[38;5;37m# |> document(as: \"some cool description\")
\e[38;5;25m┃\e[0m \e[38;5;25m ^^^^^^^^
\e[38;5;25m┃\e[0m
\
"""

assert capture_io(fn ->
assert Output.print_request_errors([validation_error]) == :ok
end) == expected_output
end
end

describe "print_configuration_errors/1" do
Expand All @@ -78,7 +111,7 @@ defmodule Xcribe.CLI.OutputTest do
]

expected_output = """
\e[42m\e[37m [ Xcribe ] Configuration Errors \e[0m
\e[42m\e[37m [ Xcribe ] Configuration errors \e[0m
\e[32m┃\e[0m
\e[32m┃\e[0m [C] → \e[34m Given json library doesn't implement needed functions
\e[32m┃\e[0m \e[38;5;240m> Config key: json_library
Expand Down Expand Up @@ -118,7 +151,7 @@ defmodule Xcribe.CLI.OutputTest do
]

expected_output = """
\e[42m\e[37m [ Xcribe ] Configuration Errors \e[0m
\e[42m\e[37m [ Xcribe ] Configuration errors \e[0m
\e[32m┃\e[0m
\e[32m┃\e[0m [C] → \e[34m When serve config is true you must confiture output to \"priv/static\" folder
\e[38;5;100m┃\e[0m
Expand Down
40 changes: 40 additions & 0 deletions test/xcribe/formatter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ defmodule Xcribe.FormatterTest do
assert output =~ "formatter_test.exs"
end

test "handle parsing and validation errors" do
parsing_error = %Error{
type: :parsing,
message: "route not found",
__meta__: %{
call: %{
description: "test name",
file: File.cwd!() <> "/test/xcribe/formatter_test.exs",
line: 1
}
}
}

validation_error = %Error{
type: :validation,
message:
"The Plug.Conn params must be valid HTTP params. A struct Elixir.Date was found!",
__meta__: %{
call: %{
description: "test name",
file: File.cwd!() <> "/test/xcribe/formatter_test.exs",
line: 1
}
}
}

Recorder.save(%Request{})
Recorder.save(parsing_error)
Recorder.save(validation_error)

output =
capture_io(fn ->
assert Formatter.handle_cast({:suite_finished, 1, 2}, nil) == {:noreply, nil}
end)

assert output =~ "route not found"
assert output =~ "formatter_test.exs"
assert output =~ "The Plug.Conn params must be valid HTTP params"
end

test "handle invalid configuration" do
Application.put_env(:xcribe, :format, :invalid)
Application.put_env(:xcribe, :json_library, Fake)
Expand Down
Loading

0 comments on commit 5bd5d19

Please sign in to comment.