From 1fc62472f7c0dcb2488209502a432f0034c61e87 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Wed, 3 Jan 2024 21:58:54 +0100 Subject: [PATCH] Fix Credo warnings, simplify code with Credo (#23) * Fix Credo warnings, simplify code with Credo * Simplify MainSocket tests - remove need for StreamingSocket --- .credo.exs | 228 ++++++++++++++++++ .github/workflows/elixir.yml | 41 +++- .github/workflows/release.yml | 43 +++- lib/xtb_client/error.ex | 5 + lib/xtb_client/messages/calendar_infos.ex | 4 +- lib/xtb_client/messages/day.ex | 26 +- lib/xtb_client/messages/margin_mode.ex | 20 +- lib/xtb_client/messages/news_infos.ex | 4 +- lib/xtb_client/messages/operation.ex | 45 ++-- lib/xtb_client/messages/period.ex | 54 ++--- lib/xtb_client/messages/profit_mode.ex | 16 +- lib/xtb_client/messages/quote_id.ex | 26 +- lib/xtb_client/messages/step_rules.ex | 4 +- lib/xtb_client/messages/symbol_info.ex | 1 + lib/xtb_client/messages/symbol_infos.ex | 4 +- lib/xtb_client/messages/trade_info.ex | 1 + lib/xtb_client/messages/trade_infos.ex | 4 +- lib/xtb_client/messages/trade_transaction.ex | 11 +- lib/xtb_client/messages/trade_type.ex | 36 +-- lib/xtb_client/messages/trading_hour.ex | 4 +- lib/xtb_client/messages/trading_hours.ex | 6 +- lib/xtb_client/messages/transaction_status.ex | 20 +- lib/xtb_client/{messages => }/rate_limit.ex | 0 .../fixtures/main_socket_e2e_fixtures.ex | 8 +- test/xtb_client/main_socket_test.exs | 61 ++--- test/xtb_client/rate_limit_test.exs | 14 +- test/xtb_client/streaming_socket_test.exs | 2 +- 27 files changed, 439 insertions(+), 249 deletions(-) create mode 100644 .credo.exs rename lib/xtb_client/{messages => }/rate_limit.ex (100%) diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..056c6a6 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,228 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/", + "priv/repo/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # + # If you don't want the `setup` and `test` macro calls in ExUnit tests + # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just + # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. + # + {Credo.Check.Design.DuplicatedCode, mass_threshold: 80, excluded_macros: []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagFIXME, []}, + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.DoubleBooleanNegation}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + + # + # External libraries checks + # + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index e448b35..245ab8c 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -15,27 +15,44 @@ jobs: runs-on: self-hosted steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Read .tool-versions + uses: marocchino/tool-versions-action@v1 + id: versions - name: Set up Elixir - uses: erlef/setup-beam@988e02bfe678367a02564f65ca2e37726dc0268f + uses: erlef/setup-beam@v1 with: - elixir-version: "1.13.1" # Define the elixir version [required] - otp-version: "24.1" # Define the OTP version [required] + elixir-version: ${{steps.versions.outputs.elixir}} + otp-version: ${{ steps.versions.outputs.erlang}} - name: Restore dependencies cache - uses: actions/cache@v3 + uses: buildjet/cache@v3 with: - path: deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: ${{ runner.os }}-mix- + path: | + deps + _build + priv/plts + key: ${{ runner.os }}-mix-v6-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix-v6- - name: Install dependencies run: mix deps.get - - name: Compile code - run: mix compile --warnings-as-errors + - name: Check Formatting + run: mix format --check-formatted + - name: Check Compile Warnings + run: mix compile --warnings-as-errors --all-warnings + - name: Check Credo Warnings + run: mix credo --strict - name: Write env file run: | touch .env.test echo XTB_API_URL="${{ secrets.XTB_API_URL }}" >> .env.test echo XTB_API_USERNAME="${{ secrets.XTB_API_USERNAME }}" >> .env.test - echo XTB_API_PASSWORD="${{ secrets.XTB_API_PASSWORD }}" >> .env.test + echo XTB_API_PASSWORD="${{ secrets.XTB_API_PASSWORD }}" >> .env.test - name: Run tests - run: mix test + run: mix test --max-failures 1 --warnings-as-errors + - name: Check Dialyzer + run: mix dialyzer + +env: + MIX_ENV: test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e0cd3c..f42cee8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,33 +13,52 @@ jobs: runs-on: self-hosted steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Read .tool-versions + uses: marocchino/tool-versions-action@v1 + id: versions - name: Set up Elixir - uses: erlef/setup-beam@988e02bfe678367a02564f65ca2e37726dc0268f + uses: erlef/setup-beam@v1 with: - elixir-version: "1.13.1" # Define the elixir version [required] - otp-version: "24.1" # Define the OTP version [required] + elixir-version: ${{steps.versions.outputs.elixir}} + otp-version: ${{ steps.versions.outputs.erlang}} - name: Restore dependencies cache - uses: actions/cache@v3 + uses: buildjet/cache@v3 with: - path: deps - key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} - restore-keys: ${{ runner.os }}-mix- + path: | + deps + _build + priv/plts + key: ${{ runner.os }}-mix-v6-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix-v6- - name: Install dependencies run: mix deps.get - - name: Compile code - run: mix compile --warnings-as-errors + - name: Check Formatting + run: mix format --check-formatted + - name: Check Compile Warnings + run: mix compile --warnings-as-errors --all-warnings + - name: Check Credo Warnings + run: mix credo --strict - name: Write env file run: | touch .env.test echo XTB_API_URL="${{ secrets.XTB_API_URL }}" >> .env.test echo XTB_API_USERNAME="${{ secrets.XTB_API_USERNAME }}" >> .env.test - echo XTB_API_PASSWORD="${{ secrets.XTB_API_PASSWORD }}" >> .env.test + echo XTB_API_PASSWORD="${{ secrets.XTB_API_PASSWORD }}" >> .env.test - name: Run tests - run: mix test + env: + MIX_ENV: test + run: mix test --max-failures 1 --warnings-as-errors + - name: Check Dialyzer + run: mix dialyzer - name: Generate documentation run: mix docs - name: Publish run: mix hex.publish --yes env: HEX_API_KEY: ${{ secrets.HEX_API_KEY }} + +env: + MIX_ENV: prod diff --git a/lib/xtb_client/error.ex b/lib/xtb_client/error.ex index 4cfe8b6..e8768d4 100644 --- a/lib/xtb_client/error.ex +++ b/lib/xtb_client/error.ex @@ -3,6 +3,11 @@ defmodule XtbClient.Error do Struct to represent errors returned by the XTB API """ + @type t :: %__MODULE__{ + code: String.t(), + message: String.t() + } + @enforce_keys [:code, :message] defstruct [:code, :message] diff --git a/lib/xtb_client/messages/calendar_infos.ex b/lib/xtb_client/messages/calendar_infos.ex index 7131fdd..bac8b04 100644 --- a/lib/xtb_client/messages/calendar_infos.ex +++ b/lib/xtb_client/messages/calendar_infos.ex @@ -21,9 +21,7 @@ defmodule XtbClient.Messages.CalendarInfos do def new(data) when is_list(data) do %__MODULE__{ - data: - data - |> Enum.map(&CalendarInfo.new(&1)) + data: Enum.map(data, &CalendarInfo.new(&1)) } end end diff --git a/lib/xtb_client/messages/day.ex b/lib/xtb_client/messages/day.ex index 2a8569b..248af2d 100644 --- a/lib/xtb_client/messages/day.ex +++ b/lib/xtb_client/messages/day.ex @@ -14,23 +14,21 @@ defmodule XtbClient.Messages.Day do @type day_code :: 1..7 + @map [ + monday: 1, + tuesday: 2, + wednesday: 3, + thursday: 4, + friday: 5, + saturday: 6, + sunday: 7 + ] + @doc """ Parse an integer value as a valid atom representing day of week. """ @spec parse(value :: day_code()) :: t() - def parse(value) when is_integer(value) and value in 1..7 do - parse_day(value) - end - - defp parse_day(value) do - case value do - 1 -> :monday - 2 -> :tuesday - 3 -> :wednesday - 4 -> :thursday - 5 -> :friday - 6 -> :saturday - 7 -> :sunday - end + for {day, value} <- @map do + def parse(unquote(value)), do: unquote(day) end end diff --git a/lib/xtb_client/messages/margin_mode.ex b/lib/xtb_client/messages/margin_mode.ex index 9341f7a..f4b698f 100644 --- a/lib/xtb_client/messages/margin_mode.ex +++ b/lib/xtb_client/messages/margin_mode.ex @@ -6,20 +6,18 @@ defmodule XtbClient.Messages.MarginMode do @type t :: :forex | :cfd_leveraged | :cfd | :hundred_and_four @type margin_code :: 101 | 102 | 103 | 104 + @map [ + forex: 101, + cfd_leveraged: 102, + cfd: 103, + hundred_and_four: 104 + ] + @doc """ Parse an integer value as a valid atom for margin mode. """ @spec parse(value :: margin_code()) :: t() - def parse(value) when is_number(value) and value in [101, 102, 103, 104] do - parse_margin_mode(value) - end - - defp parse_margin_mode(value) do - case value do - 101 -> :forex - 102 -> :cfd_leveraged - 103 -> :cfd - 104 -> :hundred_and_four - end + for {mode, value} <- @map do + def parse(unquote(value)), do: unquote(mode) end end diff --git a/lib/xtb_client/messages/news_infos.ex b/lib/xtb_client/messages/news_infos.ex index 78754f5..7dcf639 100644 --- a/lib/xtb_client/messages/news_infos.ex +++ b/lib/xtb_client/messages/news_infos.ex @@ -21,9 +21,7 @@ defmodule XtbClient.Messages.NewsInfos do def new(data) when is_list(data) do %__MODULE__{ - data: - data - |> Enum.map(&NewsInfo.new(&1)) + data: Enum.map(data, &NewsInfo.new(&1)) } end diff --git a/lib/xtb_client/messages/operation.ex b/lib/xtb_client/messages/operation.ex index 12c3f36..1abdfc1 100644 --- a/lib/xtb_client/messages/operation.ex +++ b/lib/xtb_client/messages/operation.ex @@ -15,45 +15,30 @@ defmodule XtbClient.Messages.Operation do @type operation_code :: 0..7 + @map [ + buy: 0, + sell: 1, + buy_limit: 2, + sell_limit: 3, + buy_stop: 4, + sell_stop: 5, + balance: 6, + credit: 7 + ] + @doc """ Parse an integer number as valid operation atom. """ @spec parse(value :: operation_code()) :: t() - def parse(value) when value in 0..7 do - parse_operation(value) - end - - defp parse_operation(value) do - case value do - 0 -> :buy - 1 -> :sell - 2 -> :buy_limit - 3 -> :sell_limit - 4 -> :buy_stop - 5 -> :sell_stop - 6 -> :balance - 7 -> :credit - end + for {operation, value} <- @map do + def parse(unquote(value)), do: unquote(operation) end @doc """ Format operation atom as integer value. """ @spec format(operation :: t()) :: operation_code() - def format(operation) when is_atom(operation) do - format_operation(operation) - end - - defp format_operation(operation) do - case operation do - :buy -> 0 - :sell -> 1 - :buy_limit -> 2 - :sell_limit -> 3 - :buy_stop -> 4 - :sell_stop -> 5 - :balance -> 6 - :credit -> 7 - end + for {operation, value} <- @map do + def format(unquote(operation)), do: unquote(value) end end diff --git a/lib/xtb_client/messages/period.ex b/lib/xtb_client/messages/period.ex index 585d6d5..5f71ff3 100644 --- a/lib/xtb_client/messages/period.ex +++ b/lib/xtb_client/messages/period.ex @@ -6,47 +6,31 @@ defmodule XtbClient.Messages.Period do @type t :: :m1 | :m5 | :m15 | :m30 | :h1 | :h4 | :d1 | :w1 | :mn1 @type minute_period :: 1 | 5 | 15 | 30 | 60 | 240 | 1440 | 10_080 | 43_200 - @doc """ - Formats period given as `Period` to number of minutes. - """ - @spec format(period :: t()) :: minute_period() - def format(period) when is_atom(period) do - format_period(period) - end - - defp format_period(period) do - case period do - :m1 -> 1 - :m5 -> 5 - :m15 -> 15 - :m30 -> 30 - :h1 -> 60 - :h4 -> 240 - :d1 -> 1440 - :w1 -> 10_080 - :mn1 -> 43_200 - end - end + @map [ + m1: 1, + m5: 5, + m15: 15, + m30: 30, + h1: 60, + h4: 240, + d1: 1440, + w1: 10_080, + mn1: 43_200 + ] @doc """ Parses value given as number of minutes to `Period` atom type. """ @spec parse(value :: minute_period()) :: t() - def parse(value) when is_number(value) and value > 0 do - parse_period(value) + for {period, minutes} <- @map do + def parse(unquote(minutes)), do: unquote(period) end - defp parse_period(value) do - case value do - 1 -> :m1 - 5 -> :m5 - 15 -> :m15 - 30 -> :m30 - 60 -> :h1 - 240 -> :h4 - 1440 -> :d1 - 10_080 -> :w1 - 43_200 -> :mn1 - end + @doc """ + Formats period given as `Period` to number of minutes. + """ + @spec format(period :: t()) :: minute_period() + for {period, minutes} <- @map do + def format(unquote(period)), do: unquote(minutes) end end diff --git a/lib/xtb_client/messages/profit_mode.ex b/lib/xtb_client/messages/profit_mode.ex index ecf4d6b..92a4205 100644 --- a/lib/xtb_client/messages/profit_mode.ex +++ b/lib/xtb_client/messages/profit_mode.ex @@ -6,18 +6,16 @@ defmodule XtbClient.Messages.ProfitMode do @type t :: :forex | :cfd @type profit_code :: 5 | 6 + @map [ + forex: 5, + cfd: 6 + ] + @doc """ Parse an integer value to valid atom of profit mode. """ @spec parse(value :: profit_code()) :: t() - def parse(value) when is_number(value) and value in [5, 6] do - parse_profit_mode(value) - end - - defp parse_profit_mode(value) do - case value do - 5 -> :forex - 6 -> :cfd - end + for {mode, value} <- @map do + def parse(unquote(value)), do: unquote(mode) end end diff --git a/lib/xtb_client/messages/quote_id.ex b/lib/xtb_client/messages/quote_id.ex index 4bc8503..55da421 100644 --- a/lib/xtb_client/messages/quote_id.ex +++ b/lib/xtb_client/messages/quote_id.ex @@ -4,24 +4,22 @@ defmodule XtbClient.Messages.QuoteId do """ @type t :: :fixed | :float | :depth | :cross | :five | :six - @type quote_code :: 1 | 2 | 3 | 4 | 5 | 6 + @type quote_code :: 1..6 + + @map [ + fixed: 1, + float: 2, + depth: 3, + cross: 4, + five: 5, + six: 6 + ] @doc """ Parse an integer number as valid atom for quote ID. """ @spec parse(value :: quote_code()) :: t() - def parse(value) when is_integer(value) and value in [1, 2, 3, 4, 5, 6] do - parse_quote_id(value) - end - - defp parse_quote_id(value) do - case value do - 1 -> :fixed - 2 -> :float - 3 -> :depth - 4 -> :cross - 5 -> :five - 6 -> :six - end + for {code, value} <- @map do + def parse(unquote(value)), do: unquote(code) end end diff --git a/lib/xtb_client/messages/step_rules.ex b/lib/xtb_client/messages/step_rules.ex index f1948d6..b4d8c12 100644 --- a/lib/xtb_client/messages/step_rules.ex +++ b/lib/xtb_client/messages/step_rules.ex @@ -22,9 +22,7 @@ defmodule XtbClient.Messages.StepRules do def new(data) when is_list(data) do %__MODULE__{ - data: - data - |> Enum.map(&StepRule.new(&1)) + data: Enum.map(data, &StepRule.new(&1)) } end end diff --git a/lib/xtb_client/messages/symbol_info.ex b/lib/xtb_client/messages/symbol_info.ex index 9d1cac8..ee825e0 100644 --- a/lib/xtb_client/messages/symbol_info.ex +++ b/lib/xtb_client/messages/symbol_info.ex @@ -227,6 +227,7 @@ defmodule XtbClient.Messages.SymbolInfo do trailing_enabled: nil, type: 0 + # credo:disable-for-next-line def new(%{ "ask" => ask, "bid" => bid, diff --git a/lib/xtb_client/messages/symbol_infos.ex b/lib/xtb_client/messages/symbol_infos.ex index 67f17fc..77b2124 100644 --- a/lib/xtb_client/messages/symbol_infos.ex +++ b/lib/xtb_client/messages/symbol_infos.ex @@ -21,9 +21,7 @@ defmodule XtbClient.Messages.SymbolInfos do def new(data) when is_list(data) do %__MODULE__{ - data: - data - |> Enum.map(&SymbolInfo.new(&1)) + data: Enum.map(data, &SymbolInfo.new(&1)) } end end diff --git a/lib/xtb_client/messages/trade_info.ex b/lib/xtb_client/messages/trade_info.ex index 825d938..9ad8fe8 100644 --- a/lib/xtb_client/messages/trade_info.ex +++ b/lib/xtb_client/messages/trade_info.ex @@ -158,6 +158,7 @@ defmodule XtbClient.Messages.TradeInfo do } end + # credo:disable-for-next-line def new(%{ "close_price" => close_price, "close_time" => close_time_value, diff --git a/lib/xtb_client/messages/trade_infos.ex b/lib/xtb_client/messages/trade_infos.ex index 9a1774d..6f19053 100644 --- a/lib/xtb_client/messages/trade_infos.ex +++ b/lib/xtb_client/messages/trade_infos.ex @@ -46,9 +46,7 @@ defmodule XtbClient.Messages.TradeInfos do def new(data) when is_list(data) do %__MODULE__{ - data: - data - |> Enum.map(&TradeInfo.new(&1)) + data: Enum.map(data, &TradeInfo.new(&1)) } end diff --git a/lib/xtb_client/messages/trade_transaction.ex b/lib/xtb_client/messages/trade_transaction.ex index 4f4ffce..6b71cb7 100644 --- a/lib/xtb_client/messages/trade_transaction.ex +++ b/lib/xtb_client/messages/trade_transaction.ex @@ -57,10 +57,13 @@ defmodule XtbClient.Messages.TradeTransaction do volume: 0.0 def new(%{} = params) do - params - |> Enum.reduce(%__MODULE__{}, fn {key, value}, acc -> - apply(__MODULE__, key, [acc, value]) - end) + Enum.reduce( + params, + %__MODULE__{}, + fn {key, value}, acc -> + apply(__MODULE__, key, [acc, value]) + end + ) end def operation(%__MODULE__{} = params, operation) when is_atom(operation) do diff --git a/lib/xtb_client/messages/trade_type.ex b/lib/xtb_client/messages/trade_type.ex index 92b9330..5f856dd 100644 --- a/lib/xtb_client/messages/trade_type.ex +++ b/lib/xtb_client/messages/trade_type.ex @@ -13,39 +13,27 @@ defmodule XtbClient.Messages.TradeType do @type t :: :open | :pending | :close | :modify | :delete @type trade_code :: 0..4 + @map [ + open: 0, + pending: 1, + close: 2, + modify: 3, + delete: 4 + ] + @doc """ Parse integer value as valid atom for trade type. """ @spec parse(value :: trade_code()) :: t() - def parse(value) when value in [0, 1, 2, 3, 4] do - parse_type(value) - end - - defp parse_type(value) do - case value do - 0 -> :open - 1 -> :pending - 2 -> :close - 3 -> :modify - 4 -> :delete - end + for {type, value} <- @map do + def parse(unquote(value)), do: unquote(type) end @doc """ Format atom representing trade type to integer value. """ @spec format(type :: t()) :: trade_code() - def format(type) when is_atom(type) do - format_type(type) - end - - defp format_type(type) do - case type do - :open -> 0 - :pending -> 1 - :close -> 2 - :modify -> 3 - :delete -> 4 - end + for {type, value} <- @map do + def format(unquote(type)), do: unquote(value) end end diff --git a/lib/xtb_client/messages/trading_hour.ex b/lib/xtb_client/messages/trading_hour.ex index fc4b234..04c7196 100644 --- a/lib/xtb_client/messages/trading_hour.ex +++ b/lib/xtb_client/messages/trading_hour.ex @@ -29,9 +29,9 @@ defmodule XtbClient.Messages.TradingHour do }) when is_list(quotes) and is_list(trading) do %__MODULE__{ - quotes: quotes |> Enum.map(&Quote.new(&1)), + quotes: Enum.map(quotes, &Quote.new(&1)), symbol: symbol || "", - trading: trading |> Enum.map(&Quote.new(&1)) + trading: Enum.map(trading, &Quote.new(&1)) } end end diff --git a/lib/xtb_client/messages/trading_hours.ex b/lib/xtb_client/messages/trading_hours.ex index dda3b46..6fd6066 100644 --- a/lib/xtb_client/messages/trading_hours.ex +++ b/lib/xtb_client/messages/trading_hours.ex @@ -45,8 +45,10 @@ defmodule XtbClient.Messages.TradingHours do def new(data) when is_list(data) do %__MODULE__{ data: - data - |> Enum.map(&TradingHour.new(&1)) + Enum.map( + data, + &TradingHour.new(&1) + ) } end end diff --git a/lib/xtb_client/messages/transaction_status.ex b/lib/xtb_client/messages/transaction_status.ex index 6b2ed55..2fe6237 100644 --- a/lib/xtb_client/messages/transaction_status.ex +++ b/lib/xtb_client/messages/transaction_status.ex @@ -6,20 +6,18 @@ defmodule XtbClient.Messages.TransactionStatus do @type t :: :error | :pending | :accepted | :rejected @type status_code :: 0 | 1 | 3 | 4 + @map [ + error: 0, + pending: 1, + accepted: 3, + rejected: 4 + ] + @doc """ Parse integer value as valid atom for transaction status. """ @spec parse(value :: status_code()) :: t() - def parse(value) when value in [0, 1, 3, 4] do - parse_status(value) - end - - defp parse_status(value) do - case value do - 0 -> :error - 1 -> :pending - 3 -> :accepted - 4 -> :rejected - end + for {status, value} <- @map do + def parse(unquote(value)), do: unquote(status) end end diff --git a/lib/xtb_client/messages/rate_limit.ex b/lib/xtb_client/rate_limit.ex similarity index 100% rename from lib/xtb_client/messages/rate_limit.ex rename to lib/xtb_client/rate_limit.ex diff --git a/test/support/fixtures/main_socket_e2e_fixtures.ex b/test/support/fixtures/main_socket_e2e_fixtures.ex index af104d0..a48c062 100644 --- a/test/support/fixtures/main_socket_e2e_fixtures.ex +++ b/test/support/fixtures/main_socket_e2e_fixtures.ex @@ -2,7 +2,7 @@ defmodule XtbClient.MainSocket.E2EFixtures do @moduledoc false alias XtbClient.MainSocket - alias XtbClient.Messages + alias XtbClient.Messages.{Trades, TradeTransaction} def poll_stream_session_id(server) do case MainSocket.stream_session_id(server) do @@ -16,13 +16,13 @@ defmodule XtbClient.MainSocket.E2EFixtures do end def open_trade(pid, buy_args) do - buy = Messages.TradeTransaction.Command.new(buy_args) + buy = TradeTransaction.Command.new(buy_args) MainSocket.trade_transaction(pid, buy) end def close_trade(pid, open_order_id, close_args) do # 1. way - get all opened only trades - trades_query = Messages.Trades.Query.new(true) + trades_query = Trades.Query.new(true) {:ok, result} = MainSocket.get_trades(pid, trades_query) position_to_close = Enum.find(result.data, &(&1.order_closed == open_order_id)) @@ -36,7 +36,7 @@ defmodule XtbClient.MainSocket.E2EFixtures do } ) - close = Messages.TradeTransaction.Command.new(close_args) + close = TradeTransaction.Command.new(close_args) MainSocket.trade_transaction(pid, close) end end diff --git a/test/xtb_client/main_socket_test.exs b/test/xtb_client/main_socket_test.exs index 464ad83..5229660 100644 --- a/test/xtb_client/main_socket_test.exs +++ b/test/xtb_client/main_socket_test.exs @@ -4,44 +4,38 @@ defmodule XtbClient.MainSocketTest do doctest XtbClient.MainSocket alias XtbClient.MainSocket - alias XtbClient.StreamingSocket - alias XtbClient.StreamingSocketMock - alias XtbClient.StreamingTestStoreMock - - import XtbClient.MainSocket.E2EFixtures alias XtbClient.Messages.{ BalanceInfo, - CalendarInfos, CalendarInfo, + CalendarInfos, Candle, ChartLast, ChartRange, CommissionDefinition, DateRange, MarginTrade, - NewsInfos, NewsInfo, + NewsInfos, ProfitCalculation, Quote, RateInfos, ServerTime, - StepRules, - StepRule, Step, + StepRule, + StepRules, SymbolInfo, SymbolInfos, SymbolVolume, - TickPrices, TickPrice, - TradeInfos, + TickPrices, TradeInfo, + TradeInfos, Trades, - TradeStatus, TradeTransaction, TradeTransactionStatus, - TradingHours, TradingHour, + TradingHours, UserInfo, Version } @@ -242,7 +236,7 @@ defmodule XtbClient.MainSocketTest do test "get news", %{pid: pid} do args = %{ - from: DateTime.utc_now() |> DateTime.add(-2 * 30 * 24 * 60 * 60), + from: DateTime.add(DateTime.utc_now(), -2 * 30 * 24 * 60 * 60), to: DateTime.utc_now() } @@ -288,7 +282,7 @@ defmodule XtbClient.MainSocketTest do args = %{ level: 0, symbols: ["LITECOIN"], - timestamp: DateTime.utc_now() |> DateTime.add(-2 * 60) + timestamp: DateTime.add(DateTime.utc_now(), -2 * 60) } query = TickPrices.Query.new(args) @@ -300,7 +294,7 @@ defmodule XtbClient.MainSocketTest do test "get trades history", %{pid: pid} do args = %{ - from: DateTime.utc_now() |> DateTime.add(-3 * 31 * 24 * 60 * 60), + from: DateTime.add(DateTime.utc_now(), -3 * 31 * 24 * 60 * 60), to: DateTime.utc_now() } @@ -327,29 +321,6 @@ defmodule XtbClient.MainSocketTest do test "get version", %{pid: pid} do assert {:ok, %Version{}} = MainSocket.get_version(pid) end - end - - @default_wait_time 60 * 1000 - - describe "trade transaction with async messages" do - setup :setup_main_socket - - setup %{pid: pid, params: params} do - {:ok, _store} = start_supervised(StreamingTestStoreMock) - - parent_pid = self() - Agent.update(StreamingTestStoreMock, fn _ -> %{parent_pid: parent_pid} end) - - {:ok, stream_session_id} = poll_stream_session_id(pid) - - params = - Keyword.merge(params, stream_session_id: stream_session_id, module: StreamingSocketMock) - - {:ok, streaming_pid} = StreamingSocket.start_link(params) - assert {:ok, _} = StreamingSocket.subscribe_get_trade_status(streaming_pid) - - :ok - end test "trade transaction - open and close transaction", %{pid: pid} do buy_args = %{ @@ -366,8 +337,6 @@ defmodule XtbClient.MainSocketTest do assert {:ok, %TradeTransaction{order: open_order_id}} = MainSocket.trade_transaction(pid, buy) - assert_receive {:ok, %TradeStatus{}}, @default_wait_time - status = TradeTransactionStatus.Query.new(open_order_id) assert {:ok, %TradeTransactionStatus{}} = MainSocket.trade_transaction_status(pid, status) @@ -376,8 +345,10 @@ defmodule XtbClient.MainSocketTest do assert {:ok, %TradeInfos{data: data}} = MainSocket.get_trades(pid, trades_query) position_to_close = - data - |> Enum.find(&(&1.order_closed == open_order_id)) + Enum.find( + data, + &(&1.order_closed == open_order_id) + ) close_args = %{ operation: :buy, @@ -391,8 +362,8 @@ defmodule XtbClient.MainSocketTest do close = TradeTransaction.Command.new(close_args) - assert {:ok, %TradeTransaction{}} = MainSocket.trade_transaction(pid, close) - assert_receive {:ok, %TradeStatus{order: close_order_id}}, @default_wait_time + assert {:ok, %TradeTransaction{order: close_order_id}} = + MainSocket.trade_transaction(pid, close) status = TradeTransactionStatus.Query.new(close_order_id) diff --git a/test/xtb_client/rate_limit_test.exs b/test/xtb_client/rate_limit_test.exs index f6889b0..68e3451 100644 --- a/test/xtb_client/rate_limit_test.exs +++ b/test/xtb_client/rate_limit_test.exs @@ -15,8 +15,11 @@ defmodule XtbClient.RateLimitTest do test "checks rate" do sut = - RateLimit.new(200) - |> Map.put(:time_stamp, DateTime.to_unix(DateTime.utc_now(), :millisecond)) + Map.put( + RateLimit.new(200), + :time_stamp, + DateTime.to_unix(DateTime.utc_now(), :millisecond) + ) Process.sleep(250) @@ -30,8 +33,11 @@ defmodule XtbClient.RateLimitTest do test "checks rate and sleeps" do sut = - RateLimit.new(200) - |> Map.put(:time_stamp, DateTime.to_unix(DateTime.utc_now(), :millisecond)) + Map.put( + RateLimit.new(200), + :time_stamp, + DateTime.to_unix(DateTime.utc_now(), :millisecond) + ) current_stamp = DateTime.to_unix(DateTime.utc_now(), :millisecond) sut = RateLimit.check_rate(sut) diff --git a/test/xtb_client/streaming_socket_test.exs b/test/xtb_client/streaming_socket_test.exs index c6b9196..b6774c5 100644 --- a/test/xtb_client/streaming_socket_test.exs +++ b/test/xtb_client/streaming_socket_test.exs @@ -3,8 +3,8 @@ defmodule XtbClient.StreamingSocketTest do use ExUnit.Case doctest XtbClient.StreamingSocket - alias XtbClient.Messages alias XtbClient.MainSocket + alias XtbClient.Messages alias XtbClient.StreamingSocket alias XtbClient.StreamingSocketMock alias XtbClient.StreamingTestStoreMock