Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Searching for implementations with keys #17

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

NickNeck
Copy link
Contributor

@NickNeck NickNeck commented Dec 13, 2020

Hi, Sascha

my suggestion would be to extend the config_key option so that a list is also accepted.
That would be usefull for behaviours with additional configurations.
Example:

config :my_app, :sys,
  module: MyApp.Sys.Impl
  timeout: 2_000
defmodule MyApp.Sys do
  use Knigge,
    otp_app: :my_app,
    config_key: [:sys, :module]
  ...
end

What do you think?

Kind regards, Marcus

p.s. Nice work and I love the name of the lib.

Comment on lines 12 to 14
def fetch!(%Options{implementation: {:config, otp_app, [key | keys]}}) do
otp_app
|> Application.fetch_env!(key)
|> get_in(keys)
end

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, this doesn't really play nice with the default changes from #18.

It would also fail quietly when key exists but get_in returns nil for keys.

Instead I'd would expect to receive an error along the lines of:

Unable to fetch implementation for MyFacade from config of app <otp_app> under path <keys>.

The message could be improved upon but I think you catch my drift?

@@ -305,7 +305,7 @@ defmodule Knigge.Options do
do_not_delegate: :keyword,
implementation: :module,
otp_app: :atom,
config_key: :atom,
config_key: :keys,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of introducing :keys I'd argue it's a good opportunity to introduce an "or" mechanism:

config_key: [:atom, {:list_of, :atom}]

Where valid_value?/2 would look like this:

defp valid_value?({:list_of, type}, value),
  do: is_list(value) and Enum.all?(value, &valid_value?(type, &1))

defp valid_value?(list, value) when is_list(list),
  do: Enum.any?(list, &valid_value?(&1, value))

In addition the transform function could then be extended to map config_key to always be a list (by using List.wrap/1).

Which in turn would simplify the implementation in Implementation.fetch!/1 as there no need for multiple clauses anymore (one for single atom, one for list).

@coveralls
Copy link

coveralls commented Mar 30, 2021

Coverage Status

Coverage increased (+0.8%) to 95.652% when pulling 66a7cfb on hrzndhrn:proposal/config_key into e0886d3 on sascha-wolf:main.

@escobera
Copy link

Hey @sascha-wolf what do you think is needed to merge this? I'm available to help if needed. Could really use this feature =)

sascha-wolf
sascha-wolf previously approved these changes Sep 9, 2022
Copy link
Collaborator

@sascha-wolf sascha-wolf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from some cosmetic suggestions and one small concern, LGTM 💙💜❤️🧡💛💚

Comment on lines 12 to 20
def fetch!(%Options{implementation: {:config, otp_app, [key | keys]}, default: default}) do
with nil <- otp_app |> env!(key, default) |> get(keys) do
raise ArgumentError,
message: """
could not fetch application environment #{inspect([key | keys])} \
for application #{inspect(otp_app)}\
"""
Copy link
Collaborator

@sascha-wolf sascha-wolf Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 If it's okay I'd prefer to write it like this:

Suggested change
def fetch!(%Options{implementation: {:config, otp_app, [key | keys]}, default: default}) do
with nil <- otp_app |> env!(key, default) |> get(keys) do
raise ArgumentError,
message: """
could not fetch application environment #{inspect([key | keys])} \
for application #{inspect(otp_app)}\
"""
def fetch!(%Options{implementation: {:config, otp_app, [key | keys]}, default: default}) do
otp_app
|> env!(key, default)
|> get(keys)
|> case do
nil ->
raise ArgumentError,
"could not fetch application environment #{inspect([key | keys])} for application #{inspect(otp_app)}"
implementation ->
implementation
end

|> with_defaults()
|> transform(with_env: env)
|> with_defaults()

Copy link
Collaborator

@sascha-wolf sascha-wolf Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ What's the train of thought here of swapping these two? This would actually break the delegate_at_runtime? default, since the "environment" spec gets resolved to a boolean in transform/2.

Comment on lines 349 to 351
defp valid_value?(:keys, value),
do: is_atom(value) || (is_list(value) && Enum.all?(value, &is_atom/1))

Copy link
Collaborator

@sascha-wolf sascha-wolf Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 I think we can drop this now, right?

Suggested change
defp valid_value?(:keys, value),
do: is_atom(value) || (is_list(value) && Enum.all?(value, &is_atom/1))

lib/knigge/options.ex Show resolved Hide resolved
@NickNeck
Copy link
Contributor Author

Hi, Sascha

I have made some more changes and incorporated your change requests.

I have also updated the deps and the ci.yml.

Because use Mix.Config is deprecated I have changed it to import Config. This causes the failing tests for Elixir versions smaller as 1.9.0.

Let me know if the PR is too big and how we want handle the failing tests.

Have a nice day.

@sascha-wolf
Copy link
Collaborator

Hey @NickNeck, I've to say the PR currently intimidates me. I'm also not sure why you updated the ci.yml flow?

If you think the project can derive value from the changes to ci.yml and config/ then let's extract them to a different PR so we can keep the focus on the originally proposed changes. Otherwise I don't feel comfortable approving and merging this.

Comment on lines +64 to +68
defmodule_salted WorkingBehaviour do
use Knigge,
otp_app: :knigge,
config_key: [:sys, :module]
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this test, I think it'd be beneficial for readability to deprecate config_key in favor of config_path. The Options module has a map_deprecated function (link) which we can use to map config_key to config_path while also printing a deprecation warning.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I will add this.

@NickNeck
Copy link
Contributor Author

I just update the ci.yml to test against the latest versions. I used my little git_hub_actions tool to make things simpler for me. I will make a separate PR and then you can take look if it is okay for you.

@SukhikhN
Copy link

Hello @NickNeck and @sascha-wolf!
Would you be so kind as to bring this PR to a release?

Now I fetch an implementation module from nested configuration key and pass it to Knigge directly with the :implementation option, but with this approach I can't change it in runtime.

@sascha-wolf
Copy link
Collaborator

Hello @NickNeck and @sascha-wolf! Would you be so kind as to bring this PR to a release?

I'll try to find some time this week. Life has not been kind lately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants