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

feat: add support for base64 encoding #13

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@ defmodule MyApp.Env do
end
```

## Base64 Encoding

Base64-encoded values can be read by setting the `base64` option to `true`.

```elixir
defmodule MyApp.Env do
use Mahaul,
ENCODED_STR: [type: :str, base64: true]
end
```

## Setting documentation

There is a default documentation added for each of the compile time generated function equivalents for the environment variables. However you may use the `doc` option to add a custom documentation with more details and explanations as per your needs.
Expand Down
7 changes: 7 additions & 0 deletions lib/mahaul/configs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ defmodule Mahaul.Configs do
end
end

defp validate_opt!({:base64, encoded?}, name) do
unless is_boolean(encoded?) do
raise ArgumentError,
"#{name}: expected :base64 to be a boolean, got: #{inspect(encoded?)}"
end
end

defp validate_opt!({:choices, choices}, name) do
unless is_list(choices) and not Enum.empty?(choices) do
raise ArgumentError,
Expand Down
32 changes: 29 additions & 3 deletions lib/mahaul/env.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,38 @@ defmodule Mahaul.Env do
def get_env(key, config) do
env_name = Atom.to_string(key)
env_type = Keyword.get(config, :type)
env_val = System.get_env(env_name) |> get_env_val_or_default(config)

if is_nil(env_val), do: @error_tuple, else: Parser.parse(env_val, env_type)
env_val =
System.get_env(env_name)
|> maybe_decode(config)
|> get_env_val_or_default(config)

cond do
env_val == @error_tuple -> @error_tuple
is_nil(env_val) -> @error_tuple
true -> Parser.parse(env_val, env_type)
end
end

defp maybe_decode(env_val, config) when is_binary(env_val) do
decode? = Keyword.get(config, :base64, false)

if decode? do
case Base.decode64(env_val) do
{:ok, decoded} -> decoded
:error -> @error_tuple
end
else
env_val
end
end

defp get_env_val_or_default(env_val, config, mix_env \\ Constants.mix_env()) do
defp maybe_decode(nil, _config), do: nil

defp get_env_val_or_default(env_val, config, mix_env \\ Constants.mix_env())
defp get_env_val_or_default(@error_tuple, _config, _mix_env), do: @error_tuple

defp get_env_val_or_default(env_val, config, mix_env) do
# simplify this once we remove support for `:default_dev` option
# in favour of the `:defaults` option
if Keyword.has_key?(config, :default_dev) do
Expand Down
134 changes: 133 additions & 1 deletion test/mahaul_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ defmodule MahaulTest do
{"MOCK__ENV__BOOL", "true"},
{"MOCK__ENV__PORT", "8080"},
{"MOCK__ENV__HOST", "//example.com"},
{"MOCK__ENV__URI", "https://example.com/something"}
{"MOCK__ENV__URI", "https://example.com/something"},
{"MOCK__ENV__STR_ENCODED", Base.encode64("__MOCK__VAL1__")},
{"MOCK__ENV__ENUM_ENCODED", Base.encode64("__MOCK__VAL2__")},
{"MOCK__ENV__NUM_ENCODED", Base.encode64("10.10")},
{"MOCK__ENV__INT_ENCODED", Base.encode64("10")},
{"MOCK__ENV__BOOL_ENCODED", Base.encode64("true")},
{"MOCK__ENV__PORT_ENCODED", Base.encode64("8080")},
{"MOCK__ENV__HOST_ENCODED", Base.encode64("//example.com")},
{"MOCK__ENV__URI_ENCODED", Base.encode64("https://example.com/something")}
]

setup do
Expand All @@ -31,6 +39,8 @@ defmodule MahaulTest do
{"MOCK__ENV: expected options to be a non-empty keyword list, got: []", [MOCK__ENV: []]},
{"MOCK__ENV: expected :type to be one of [:str, :enum, :num, :int, :bool, :port, :host, :uri], got: :invalid",
[MOCK__ENV: [type: :invalid]]},
{"MOCK__ENV: expected :base64 to be a boolean, got: \"false\"",
[MOCK__ENV: [type: :str, base64: "false"]]},
{"MOCK__ENV: missing required options [:type]", [MOCK__ENV: [default: "__MOCK__"]]},
{"MOCK__ENV: expected :choices to be a non-empty list, got: true",
[MOCK__ENV: [type: :str, choices: true]]},
Expand Down Expand Up @@ -95,6 +105,22 @@ defmodule MahaulTest do
assert {:ok} = Env1.validate()
end

test "should return success for valid encoded environment variables" do
defmodule Env1.Encoded do
use Mahaul,
MOCK__ENV__STR_ENCODED: [type: :str, base64: true],
MOCK__ENV__ENUM_ENCODED: [type: :enum, base64: true],
MOCK__ENV__NUM_ENCODED: [type: :num, base64: true],
MOCK__ENV__INT_ENCODED: [type: :int, base64: true],
MOCK__ENV__BOOL_ENCODED: [type: :bool, base64: true],
MOCK__ENV__PORT_ENCODED: [type: :port, base64: true],
MOCK__ENV__HOST_ENCODED: [type: :host, base64: true],
MOCK__ENV__URI_ENCODED: [type: :uri, base64: true]
end

assert {:ok} = Env1.Encoded.validate()
end

test "should return error for all invalid environment variables" do
defmodule Env2 do
use Mahaul,
Expand Down Expand Up @@ -123,6 +149,40 @@ defmodule MahaulTest do
"MOCK__ENV__HOST\n" <>
"MOCK__ENV__URI"
end

test "should return error for all invalid encoded environment variables" do
defmodule Env2.Encoded do
use Mahaul,
MOCK__ENV__MISSING_ENCODED: [type: :str, base64: true],
MOCK__ENV__NUM_ENCODED: [type: :int, base64: true],
MOCK__ENV__INT_ENCODED: [type: :bool, base64: true],
MOCK__ENV__BOOL_ENCODED: [type: :num, base64: true],
MOCK__ENV__PORT_ENCODED: [type: :host, base64: true],
MOCK__ENV__HOST_ENCODED: [type: :uri, base64: true],
MOCK__ENV__URI_ENCODED: [type: :int, base64: true]
end

fun = fn ->
assert {:error,
"MOCK__ENV__MISSING_ENCODED\n" <>
"MOCK__ENV__NUM_ENCODED\n" <>
"MOCK__ENV__INT_ENCODED\n" <>
"MOCK__ENV__BOOL_ENCODED\n" <>
"MOCK__ENV__PORT_ENCODED\n" <>
"MOCK__ENV__HOST_ENCODED\n" <> "MOCK__ENV__URI_ENCODED"} =
Env2.Encoded.validate()
end

assert capture_log(fun) =~
"missing or invalid environment variables.\n" <>
"MOCK__ENV__MISSING_ENCODED\n" <>
"MOCK__ENV__NUM_ENCODED\n" <>
"MOCK__ENV__INT_ENCODED\n" <>
"MOCK__ENV__BOOL_ENCODED\n" <>
"MOCK__ENV__PORT_ENCODED\n" <>
"MOCK__ENV__HOST_ENCODED\n" <>
"MOCK__ENV__URI_ENCODED"
end
end

describe "validate!/0" do
Expand All @@ -146,6 +206,26 @@ defmodule MahaulTest do
capture_log(fun)
end

test "should not raise exception for valid encoded environment variables" do
defmodule Env3.Encoded do
use Mahaul,
MOCK__ENV__STR_ENCODED: [type: :str, base64: true],
MOCK__ENV__ENUM_ENCODED: [type: :enum, base64: true],
MOCK__ENV__NUM_ENCODED: [type: :num, base64: true],
MOCK__ENV__INT_ENCODED: [type: :int, base64: true],
MOCK__ENV__BOOL_ENCODED: [type: :bool, base64: true],
MOCK__ENV__PORT_ENCODED: [type: :port, base64: true],
MOCK__ENV__HOST_ENCODED: [type: :host, base64: true],
MOCK__ENV__URI_ENCODED: [type: :uri, base64: true]
end

fun = fn ->
assert :ok = Env3.Encoded.validate!()
end

capture_log(fun)
end

test "should raise exception for invalid environment variables" do
defmodule Env4 do
use Mahaul,
Expand Down Expand Up @@ -174,6 +254,35 @@ defmodule MahaulTest do
"MOCK__ENV__HOST\n" <>
"MOCK__ENV__URI"
end

test "should raise exception for invalid encoded environment variables" do
defmodule Env4.Encoded do
use Mahaul,
MOCK__ENV__MISSING_ENCODED: [type: :str, base64: true],
MOCK__ENV__NUM_ENCODED: [type: :int, base64: true],
MOCK__ENV__INT_ENCODED: [type: :bool, base64: true],
MOCK__ENV__BOOL_ENCODED: [type: :num, base64: true],
MOCK__ENV__PORT_ENCODED: [type: :host, base64: true],
MOCK__ENV__HOST_ENCODED: [type: :uri, base64: true],
MOCK__ENV__URI_ENCODED: [type: :int, base64: true]
end

fun = fn ->
assert_raise RuntimeError, "Invalid environment variables!", fn ->
Env4.Encoded.validate!()
end
end

assert capture_log(fun) =~
"missing or invalid environment variables.\n" <>
"MOCK__ENV__MISSING_ENCODED\n" <>
"MOCK__ENV__NUM_ENCODED\n" <>
"MOCK__ENV__INT_ENCODED\n" <>
"MOCK__ENV__BOOL_ENCODED\n" <>
"MOCK__ENV__PORT_ENCODED\n" <>
"MOCK__ENV__HOST_ENCODED\n" <>
"MOCK__ENV__URI_ENCODED"
end
end

describe "accessing environment variables" do
Expand All @@ -200,6 +309,29 @@ defmodule MahaulTest do
assert "https://example.com/something" = Env5.mock__env__uri()
end

test "should work when encoded" do
defmodule Env5.Encoded do
use Mahaul,
MOCK__ENV__STR_ENCODED: [type: :str, base64: true],
MOCK__ENV__ENUM_ENCODED: [type: :enum, base64: true],
MOCK__ENV__NUM_ENCODED: [type: :num, base64: true],
MOCK__ENV__INT_ENCODED: [type: :int, base64: true],
MOCK__ENV__BOOL_ENCODED: [type: :bool, base64: true],
MOCK__ENV__PORT_ENCODED: [type: :port, base64: true],
MOCK__ENV__HOST_ENCODED: [type: :host, base64: true],
MOCK__ENV__URI_ENCODED: [type: :uri, base64: true]
end

assert "__MOCK__VAL1__" = Env5.Encoded.mock__env__str_encoded()
assert :__MOCK__VAL2__ = Env5.Encoded.mock__env__enum_encoded()
assert 10.10 = Env5.Encoded.mock__env__num_encoded()
assert 10 = Env5.Encoded.mock__env__int_encoded()
assert true = Env5.Encoded.mock__env__bool_encoded()
assert 8080 = Env5.Encoded.mock__env__port_encoded()
assert "//example.com" = Env5.Encoded.mock__env__host_encoded()
assert "https://example.com/something" = Env5.Encoded.mock__env__uri_encoded()
end

test "should return default values" do
defmodule Env6 do
use Mahaul,
Expand Down