diff --git a/CHANGELOG.md b/CHANGELOG.md index be77f19..de1f8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v4.0.2 - 2021-12-24 + +- Add :consistent_read option in scan_opts spec +- Add `code_quality` alias +- Adds dialyxir to dev dependencies +- Various credo and dialyzer fixes +- Fixes typos in documentation and README + ## v4.0.1 - 2021-04-26 - Update dependencies diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28ed303..5662efd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,8 +6,13 @@ Contributions to ExAws.Dynamo are always welcome! For contributions to this part Before submitting any PR, please make sure that the code is adequately tested, formatted, and checked for other issues... in other words, please run ```bash -mix format -mix credo +# a convenient alias for runnin `mix format`, `mix credo --strict`, and `mix dialyzer` +mix code_quality +``` + +and + +```bash mix test ``` diff --git a/README.md b/README.md index 1d97583..9ef7c8b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Documentation for **ExAwsDynamo** can be found at [https://hexdocs.pm/ex_aws_dyn ### DynamoDB Local -If you are running this module against a local development instance of DynamoDB, you'll want to make sure that you have installed the latest version, `1.15.0` (released 2021-02-08). You can find links to download the latest version [here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html). +If you are running this module against a local development instance of DynamoDB, you'll want to make sure that you have installed the latest version, `1.17.2` (released 2021-12-16). You can find links to download the latest version [here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html). ## Configuration diff --git a/lib/ex_aws/dynamo.ex b/lib/ex_aws/dynamo.ex index 99e8ec4..60c1a66 100644 --- a/lib/ex_aws/dynamo.ex +++ b/lib/ex_aws/dynamo.ex @@ -62,6 +62,8 @@ defmodule ExAws.Dynamo do import ExAws.Utils, only: [camelize: 1, camelize_keys: 1, camelize_keys: 2, upcase: 1] alias __MODULE__ + alias ExAws.Dynamo.{Decoder, Lazy} + alias ExAws.Operation.JSON @nested_opts [:exclusive_start_key, :expression_attribute_values, :expression_attribute_names] @upcase_opts [:return_values, :return_item_collection_metrics, :select, :total_segments] @@ -125,8 +127,8 @@ defmodule ExAws.Dynamo do |> Dynamo.decode_item(as: User) ``` """ - @spec decode_item(Map.t()) :: Map.t() - @spec decode_item(Map.t(), as: atom) :: Map.t() + @spec decode_item(map()) :: map() + @spec decode_item(map(), as: atom) :: map() def decode_item(item, opts \\ []) def decode_item(%{"Items" => items}, opts) do @@ -138,12 +140,12 @@ defmodule ExAws.Dynamo do end def decode_item(item, opts) do - ExAws.Dynamo.Decoder.decode(item, opts) + Decoder.decode(item, opts) end @doc "List tables" - @spec list_tables() :: ExAws.Operation.JSON.t() - def list_tables() do + @spec list_tables() :: JSON.t() + def list_tables do request(:list_tables, %{}) end @@ -163,7 +165,7 @@ defmodule ExAws.Dynamo do read_capacity :: pos_integer, write_capacity :: pos_integer, billing_mode :: dynamo_billing_types - ) :: ExAws.Operation.JSON.t() + ) :: JSON.t() def create_table( name, primary_key, @@ -252,10 +254,10 @@ defmodule ExAws.Dynamo do key_definitions :: key_definitions, read_capacity :: pos_integer, write_capacity :: pos_integer, - global_indexes :: [Map.t()], - local_indexes :: [Map.t()], + global_indexes :: [map()], + local_indexes :: [map()], billing_mode :: dynamo_billing_types - ) :: ExAws.ExAws.Operation.JSON.t() + ) :: JSON.t() def create_table( name, key_schema, @@ -303,7 +305,7 @@ defmodule ExAws.Dynamo do read_capacity :: pos_integer, write_capacity :: pos_integer, billing_mode :: dynamo_billing_types - ) :: Map.t() + ) :: map() defp build_billing_mode(read_capacity, write_capacity, :provisioned) do %{ "BillingMode" => "PROVISIONED", @@ -320,14 +322,14 @@ defmodule ExAws.Dynamo do end @doc "Describe table" - @spec describe_table(name :: binary) :: ExAws.Operation.JSON.t() + @spec describe_table(name :: binary) :: JSON.t() def describe_table(name) do request(:describe_table, %{"TableName" => name}) end @doc "Update Table" - @spec update_table(name :: binary, attributes :: Keyword.t() | Map.t()) :: - ExAws.Operation.JSON.t() + @spec update_table(name :: binary, attributes :: Keyword.t() | map()) :: + JSON.t() def update_table(name, attributes) do data = attributes @@ -338,7 +340,7 @@ defmodule ExAws.Dynamo do request(:update_table, data) end - @spec maybe_convert_billing_mode(attributes :: Keyword.t() | Map.t()) :: Keyword.t() | Map.t() + @spec maybe_convert_billing_mode(attributes :: Keyword.t() | map()) :: Keyword.t() | map() defp maybe_convert_billing_mode(attributes) do case attributes[:billing_mode] do nil -> attributes @@ -346,16 +348,16 @@ defmodule ExAws.Dynamo do end end - @spec convert_billing_mode(attributes :: Keyword.t() | Map.t(), dynamo_billing_types) :: - Keyword.t() | Map.t() + @spec convert_billing_mode(attributes :: Keyword.t() | map(), dynamo_billing_types) :: + Keyword.t() | map() defp convert_billing_mode(attributes, :provisioned), do: do_convert_billing_mode(attributes, "PROVISIONED") defp convert_billing_mode(attributes, :pay_per_request), do: do_convert_billing_mode(attributes, "PAY_PER_REQUEST") - @spec do_convert_billing_mode(attributes :: Keyword.t() | Map.t(), value :: String.t()) :: - Keyword.t() | Map.t() + @spec do_convert_billing_mode(attributes :: Keyword.t() | map(), value :: String.t()) :: + Keyword.t() | map() defp do_convert_billing_mode(attributes, value) when is_map(attributes), do: Map.replace!(attributes, :billing_mode, value) @@ -363,21 +365,21 @@ defmodule ExAws.Dynamo do do: Keyword.replace!(attributes, :billing_mode, value) @doc "Delete Table" - @spec delete_table(table :: binary) :: ExAws.Operation.JSON.t() + @spec delete_table(table :: binary) :: JSON.t() def delete_table(table) do request(:delete_table, %{"TableName" => table}) end @doc "Update time to live" @spec update_time_to_live(table :: binary, ttl_attribute :: binary, enabled :: boolean) :: - ExAws.Operation.JSON.t() + JSON.t() def update_time_to_live(table, ttl_attribute, enabled) do data = build_time_to_live(ttl_attribute, enabled) |> Map.merge(%{"TableName" => table}) request(:update_time_to_live, data) end - @spec build_time_to_live(ttl_attribute :: binary, enabled :: boolean) :: Map.t() + @spec build_time_to_live(ttl_attribute :: binary, enabled :: boolean) :: map() defp build_time_to_live("", _enabled) do %{} end @@ -396,7 +398,7 @@ defmodule ExAws.Dynamo do end @doc "Describe time to live" - @spec describe_time_to_live(table :: binary) :: ExAws.Operation.JSON.t() + @spec describe_time_to_live(table :: binary) :: JSON.t() def describe_time_to_live(table) do request(:describe_time_to_live, %{"TableName" => table}) end @@ -437,15 +439,15 @@ defmodule ExAws.Dynamo do | {:select, select_vals} | {:total_segments, pos_integer} ] - @spec scan(table_name :: table_name) :: ExAws.Operation.JSON.t() - @spec scan(table_name :: table_name, opts :: scan_opts) :: ExAws.Operation.JSON.t() + @spec scan(table_name :: table_name) :: JSON.t() + @spec scan(table_name :: table_name, opts :: scan_opts) :: JSON.t() def scan(name, opts \\ []) do data = opts |> build_opts() |> Map.merge(%{"TableName" => name}) - request(:scan, data, %{stream_builder: &ExAws.Dynamo.Lazy.stream_scan(name, opts, &1)}) + request(:scan, data, %{stream_builder: &Lazy.stream_scan(name, opts, &1)}) end @doc """ @@ -477,15 +479,15 @@ defmodule ExAws.Dynamo do | {:scan_index_forward, boolean} | {:select, select_vals} ] - @spec query(table_name :: table_name) :: ExAws.Operation.JSON.t() - @spec query(table_name :: table_name, opts :: query_opts) :: ExAws.Operation.JSON.t() + @spec query(table_name :: table_name) :: JSON.t() + @spec query(table_name :: table_name, opts :: query_opts) :: JSON.t() def query(name, opts \\ []) do data = opts |> build_opts() |> Map.merge(%{"TableName" => name}) - request(:query, data, %{stream_builder: &ExAws.Dynamo.Lazy.stream_query(name, opts, &1)}) + request(:query, data, %{stream_builder: &Lazy.stream_query(name, opts, &1)}) end @doc """ @@ -529,9 +531,9 @@ defmodule ExAws.Dynamo do | {:expression_attribute_names, expression_attribute_names_vals} | {:projection_expression, binary} ] - @spec batch_get_item(%{table_name => get_item}) :: ExAws.Operation.JSON.t() + @spec batch_get_item(%{table_name => get_item}) :: JSON.t() @spec batch_get_item(%{table_name => get_item}, opts :: batch_get_item_opts) :: - ExAws.Operation.JSON.t() + JSON.t() def batch_get_item(data, opts \\ []) do request_items = data @@ -571,9 +573,9 @@ defmodule ExAws.Dynamo do | {:return_item_collection_metrics, return_item_collection_metrics_vals} | {:return_values, return_values_vals} ] - @spec put_item(table_name :: table_name, record :: map()) :: ExAws.Operation.JSON.t() + @spec put_item(table_name :: table_name, record :: map()) :: JSON.t() @spec put_item(table_name :: table_name, record :: map(), opts :: put_item_opts) :: - ExAws.Operation.JSON.t() + JSON.t() def put_item(name, record, opts \\ []) do data = opts @@ -603,9 +605,9 @@ defmodule ExAws.Dynamo do {:return_consumed_capacity, return_consumed_capacity_vals} | {:return_item_collection_metrics, return_item_collection_metrics_vals} ] - @spec batch_write_item(%{table_name => [write_item]}) :: ExAws.Operation.JSON.t() + @spec batch_write_item(%{table_name => [write_item]}) :: JSON.t() @spec batch_write_item(%{table_name => [write_item]}, opts :: batch_write_item_opts) :: - ExAws.Operation.JSON.t() + JSON.t() def batch_write_item(data, opts \\ []) do request_items = data @@ -638,9 +640,9 @@ defmodule ExAws.Dynamo do | {:projection_expression, binary} | {:return_consumed_capacity, return_consumed_capacity_vals} ] - @spec get_item(table_name :: table_name, primary_key :: primary_key) :: ExAws.Operation.JSON.t() + @spec get_item(table_name :: table_name, primary_key :: primary_key) :: JSON.t() @spec get_item(table_name :: table_name, primary_key :: primary_key, opts :: get_item_opts) :: - ExAws.Operation.JSON.t() + JSON.t() def get_item(name, primary_key, opts \\ []) do data = opts @@ -672,7 +674,7 @@ defmodule ExAws.Dynamo do table_name :: table_name, primary_key :: primary_key, opts :: update_item_opts - ) :: ExAws.Operation.JSON.t() + ) :: JSON.t() def update_item(table_name, primary_key, update_opts) do data = update_opts @@ -695,12 +697,12 @@ defmodule ExAws.Dynamo do | {:return_values, return_values_vals} ] @spec delete_item(table_name :: table_name, primary_key :: primary_key) :: - ExAws.Operation.JSON.t() + JSON.t() @spec delete_item( table_name :: table_name, primary_key :: primary_key, opts :: delete_item_opts - ) :: ExAws.Operation.JSON.t() + ) :: JSON.t() def delete_item(name, primary_key, opts \\ []) do data = opts @@ -727,8 +729,8 @@ defmodule ExAws.Dynamo do ] @spec transact_get_items(items :: [transact_get_item], transact_get_items_opts) :: - ExAws.Operation.JSON.t() - @spec transact_get_items(items :: [transact_get_item]) :: ExAws.Operation.JSON.t() + JSON.t() + @spec transact_get_items(items :: [transact_get_item]) :: JSON.t() @doc """ A synchronous operation that retrieves multiple items from one or more tables (but not from indexes) in a single account and region @@ -801,8 +803,8 @@ defmodule ExAws.Dynamo do A synchronous write operation that groups up to 25 action requests """ @spec transact_write_items(items :: [transact_write_item], transact_write_items_opts) :: - ExAws.Operation.JSON.t() - @spec transact_write_items(items :: [transact_write_item]) :: ExAws.Operation.JSON.t() + JSON.t() + @spec transact_write_items(items :: [transact_write_item]) :: JSON.t() def transact_write_items(items, opts \\ []) do data = opts @@ -896,7 +898,7 @@ defmodule ExAws.Dynamo do |> Atom.to_string() |> Macro.camelize() - ExAws.Operation.JSON.new( + JSON.new( :dynamodb, %{ data: data, diff --git a/lib/ex_aws/dynamo/decoder.ex b/lib/ex_aws/dynamo/decoder.ex index 1560d30..bd822d5 100644 --- a/lib/ex_aws/dynamo/decoder.ex +++ b/lib/ex_aws/dynamo/decoder.ex @@ -8,11 +8,14 @@ defmodule ExAws.Dynamo.Decoder do This is important for handling nested maps if you wanted the nested maps to have atom keys. """ + + alias ExAws.Dynamo.Decodable + def decode(item, as: struct_module) do item |> decode |> binary_map_to_struct(struct_module) - |> ExAws.Dynamo.Decodable.decode() + |> Decodable.decode() end @doc """ @@ -57,11 +60,9 @@ defmodule ExAws.Dynamo.Decoder do @doc "Attempts to convert a number to a float, and then an integer" def binary_to_number(binary) when is_binary(binary) do - try do - String.to_float(binary) - rescue - ArgumentError -> String.to_integer(binary) - end + String.to_float(binary) + rescue + ArgumentError -> String.to_integer(binary) end def binary_to_number(binary), do: binary diff --git a/lib/ex_aws/dynamo/encoder.ex b/lib/ex_aws/dynamo/encoder.ex index ecdd553..8182167 100644 --- a/lib/ex_aws/dynamo/encoder.ex +++ b/lib/ex_aws/dynamo/encoder.ex @@ -16,6 +16,8 @@ defmodule ExAws.Dynamo.Encoder do This is handled via the ExAws.Dynamo.Encodable protocol. """ + alias ExAws.Dynamo.Encodable + # These functions exist to ensure that encoding is idempotent. def encode(value), do: encode(value, []) def encode(%{"B" => _} = val, _), do: val @@ -29,19 +31,17 @@ defmodule ExAws.Dynamo.Encoder do def encode(%{"S" => _} = val, _), do: val def encode(%{"SS" => _} = val, _), do: val - def encode(value, options) do - ExAws.Dynamo.Encodable.encode(value, options) - end + def encode(value, options), do: Encodable.encode(value, options) # Use this in case you want to encode something already in Dynamo format # for some reason I cannot fathom. If you find yourself using this, please open an issue # so I can find out why and better support this. def encode!(value, options \\ []) do - ExAws.Dynamo.Encodable.encode(value, options) + Encodable.encode(value, options) end def encode_root(value, options \\ []) do - case ExAws.Dynamo.Encodable.encode(value, options) do + case Encodable.encode(value, options) do %{"M" => value} -> value %{"L" => value} -> value end diff --git a/mix.exs b/mix.exs index 6183977..e23f08e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule ExAws.Dynamo.Mixfile do use Mix.Project - @version "4.0.1" + @version "4.0.2" @service "dynamo" @url "https://github.com/ex-aws/ex_aws_#{@service}" @name __MODULE__ |> Module.split() |> Enum.take(2) |> Enum.join(".") @@ -16,7 +16,8 @@ defmodule ExAws.Dynamo.Mixfile do deps: deps(), name: @name, package: package(), - docs: docs() + docs: docs(), + aliases: aliases() ] end @@ -24,7 +25,7 @@ defmodule ExAws.Dynamo.Mixfile do [ description: "#{@name} service package", files: ["lib", "config", "mix.exs", "README*", "CHANGELOG*"], - maintainers: ["Darren Klein", "Franko Franicevich", "Gilad Barkan", "Ben Wilson"], + maintainers: ["Darren Klein", "Ben Wilson"], licenses: ["MIT"], links: %{ Changelog: "https://hexdocs.pm/ex_aws_dynamo/changelog.html", @@ -45,6 +46,7 @@ defmodule ExAws.Dynamo.Mixfile do defp deps do [ {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev}, {:hackney, ">= 0.0.0", only: [:dev, :test]}, {:jason, ">= 0.0.0", only: [:dev, :test]}, @@ -73,4 +75,10 @@ defmodule ExAws.Dynamo.Mixfile do formatters: ["html"] ] end + + defp aliases do + [ + code_quality: ["format", "credo --strict", "dialyzer"] + ] + end end diff --git a/mix.lock b/mix.lock index 1cea957..de7ea25 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,10 @@ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm", "e3be2bc3ae67781db529b80aa7e7c49904a988596e2dbff897425b48b3581161"}, "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_aws": {:hex, :ex_aws, "2.2.1", "ab8fa0e5d6d9f485ff261b99d6b932779ec994b6e4b434495c314b179da09669", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "026b085b90b3dd56a594cd6856fdf51191151aac77b5ae5fa281e5896fc21b2e"}, "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, diff --git a/test/support/ddb_local.ex b/test/support/ddb_local.ex index 6b6c984..d776e8b 100644 --- a/test/support/ddb_local.ex +++ b/test/support/ddb_local.ex @@ -15,7 +15,7 @@ defmodule DDBLocal do :ok end - def try_connect() do + def try_connect do port = get_port() if is_nil(port) do @@ -28,5 +28,5 @@ defmodule DDBLocal do end end - def get_port(), do: Application.get_env(:ex_aws, :dynamodb, [])[:port] + def get_port, do: Application.get_env(:ex_aws, :dynamodb, [])[:port] end