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

Refactor result type #50

Open
wants to merge 9 commits into
base: fix/#38-doc-and-readme
Choose a base branch
from
Open
6 changes: 3 additions & 3 deletions lib/qr_code/format_version.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ defmodule QRCode.FormatVersion do
40 => [[1, 0, 0], [1, 0, 1], [1, 0, 0], [0, 1, 1], [0, 0, 0], [1, 0, 1]]
}

@spec put_information(QR.t()) :: Result.t(String.t(), QR.t())
@spec put_information(QR.t()) :: {:ok, QR.t()} | {:error, String.t()}
def put_information(
%QR{matrix: matrix, version: version, ecc_level: ecc_level, mask_num: mask_num} = qr
)
Expand All @@ -105,7 +105,7 @@ defmodule QRCode.FormatVersion do
end

@spec set_format_info(Matrix.t(), QR.level(), QR.mask_num(), QR.version()) ::
Result.t(String.t(), Matrix.t())
{:ok, Matrix.t()} | {:error, String.t()}
def set_format_info(matrix, table_level, mask_num, version) do
{row_left, row_right, col_top, col_bottom} = information_string(table_level, mask_num)

Expand All @@ -116,7 +116,7 @@ defmodule QRCode.FormatVersion do
|> Result.and_then(&Matrix.update_col(&1, col_bottom, {4 * version + 10, 8}))
end

@spec set_version_info(Matrix.t(), QR.version()) :: Result.t(String.t(), Matrix.t())
@spec set_version_info(Matrix.t(), QR.version()) :: {:ok, Matrix.t()} | {:error, String.t()}
def set_version_info(matrix, version) when version < 7 do
Result.ok(matrix)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/qr_code/mode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule QRCode.Mode do
alias QRCode.QR
import QRCode.QR, only: [mode: 1]

@spec select(QR.t(), QR.mode()) :: Result.t(String.t(), QR.t())
@spec select(QR.t(), QR.mode()) :: {:ok, QR.t()} | {:error, String.t()}
def select(qr, mode) when mode(mode) do
qr
|> put_mode(mode)
Expand Down
22 changes: 13 additions & 9 deletions lib/qr_code/placement.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ defmodule QRCode.Placement do

@correct_separator Vector.row(8)

@spec put_patterns(QR.t()) :: Result.t(String.t(), QR.t())
@spec put_patterns(QR.t()) :: {:ok, QR.t()} | {:error, String.t()}
def put_patterns(%QR{version: version, message: message} = qr) when version(version) do
size = (version - 1) * 4 + 21

Expand All @@ -87,7 +87,7 @@ defmodule QRCode.Placement do
|> Result.map(fn matrix -> %{qr | matrix: matrix} end)
end

@spec replace_placeholders(QR.t()) :: Result.t(String.t(), QR.t())
@spec replace_placeholders(QR.t()) :: {:ok, QR.t()} | {:error, String.t()}
def replace_placeholders(%QR{matrix: matrix, version: version} = qr) when version(version) do
matrix
|> add_finders(version)
Expand All @@ -99,15 +99,17 @@ defmodule QRCode.Placement do
|> Result.map(fn matrix -> %{qr | matrix: matrix} end)
end

@spec add_finders(Matrix.t(), QR.version(), Matrix.t()) :: Result.t(String.t(), Matrix.t())
@spec add_finders(Matrix.t(), QR.version(), Matrix.t()) ::
{:ok, Matrix.t()} | {:error, String.t()}
def add_finders(matrix, version, finder \\ @correct_finder) do
matrix
|> Matrix.update(finder, {0, 0})
|> Result.and_then(&Matrix.update(&1, finder, {0, 4 * version + 10}))
|> Result.and_then(&Matrix.update(&1, finder, {4 * version + 10, 0}))
end

@spec add_separators(Matrix.t(), QR.version(), Vector.t()) :: Result.t(String.t(), Matrix.t())
@spec add_separators(Matrix.t(), QR.version(), Vector.t()) ::
{:ok, Matrix.t()} | {:error, String.t()}
def add_separators(matrix, version, row \\ @correct_separator) do
col = Vector.transpose(row)

Expand All @@ -121,7 +123,7 @@ defmodule QRCode.Placement do
end

@spec add_reserved_areas(Matrix.t(), QR.version(), non_neg_integer()) ::
Result.t(String.t(), Matrix.t())
{:ok, Matrix.t()} | {:error, String.t()}
def add_reserved_areas(matrix, version, val \\ 0)

def add_reserved_areas(matrix, version, val) when version < 7 do
Expand All @@ -134,15 +136,16 @@ defmodule QRCode.Placement do
|> add_reserve_via(version, val)
end

@spec add_timings(Matrix.t(), QR.version()) :: Result.t(String.t(), Matrix.t())
@spec add_timings(Matrix.t(), QR.version()) :: {:ok, Matrix.t()} | {:error, String.t()}
def add_timings(matrix, version) do
row = get_timing_row(version)

[row |> Result.and_then(&Matrix.update_row(matrix, &1, {6, 8})), row]
|> Result.and_then_x(&Matrix.update_col(&1, Vector.transpose(&2), {8, 6}))
end

@spec add_timings(Matrix.t(), QR.version(), pos_integer()) :: Result.t(String.t(), Matrix.t())
@spec add_timings(Matrix.t(), QR.version(), pos_integer()) ::
{:ok, Matrix.t()} | {:error, String.t()}
def add_timings(matrix, version, val) do
size = 4 * version + 1
row = size |> Vector.row(val)
Expand All @@ -152,7 +155,8 @@ defmodule QRCode.Placement do
|> Result.and_then(&Matrix.update_col(&1, Vector.transpose(row), {8, 6}))
end

@spec add_alignments(Matrix.t(), QR.version(), Matrix.t()) :: Result.t(String.t(), Matrix.t())
@spec add_alignments(Matrix.t(), QR.version(), Matrix.t()) ::
{:ok, Matrix.t()} | {:error, String.t()}
def add_alignments(matrix, version, alignment \\ @correct_alignment)
def add_alignments(matrix, 1, _alignment), do: Result.ok(matrix)

Expand All @@ -174,7 +178,7 @@ defmodule QRCode.Placement do
end

@spec add_dark_module(Matrix.t(), QR.version(), pos_integer()) ::
Result.t(String.t(), Matrix.t())
{:ok, Matrix.t()} | {:error, String.t()}
def add_dark_module(matrix, version, val \\ 1) do
Matrix.update_element(matrix, val, {4 * version + 9, 8})
end
Expand Down
4 changes: 2 additions & 2 deletions lib/qr_code/qr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ defmodule QRCode.QR do

For saving QR code to svg file, you have to render it first and then save it:

iex> qr = QRCode.QR.create("Hello World", :high)
iex> {:ok, qr} = QRCode.QR.create("Hello World", :high)
iex> qr |> QRCode.render() |> QRCode.save("hello.svg")
{:ok, "hello.svg"}

The svg file will be saved into your project directory.
"""
@spec create(String.t(), level(), mode()) :: Result.t(String.t(), t())
@spec create(String.t(), level(), mode()) :: {:ok, t()} | {:error, String.t()}
def create(orig, level \\ :low, mode \\ :byte) when level(level) and mode(mode) do
%__MODULE__{orig: orig, ecc_level: level}
|> QRCode.Mode.select(mode)
Expand Down
29 changes: 12 additions & 17 deletions lib/qr_code/render.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ defmodule QRCode.Render do
Render common module.
"""

@type image_format() :: :png | :svg

alias QRCode.QR
alias QRCode.Render.{PngSettings, SvgSettings}

@doc """
Render QR matrix to `svg` or `png` binary representation with default settings.
"""
@spec render(Result.t(String.t(), binary()), :png | :svg) :: Result.t(String.t(), binary())
@spec render(QR.t(), image_format()) :: String.t()
def render(qr, :svg) do
render(qr, :svg, %SvgSettings{})
end
Expand Down Expand Up @@ -44,8 +47,7 @@ defmodule QRCode.Render do
| qrcode_color | string or {r, g, b} | "#000000" | sets color of QR |
```
"""
@spec render(Result.t(String.t(), binary()), atom(), SvgSettings.t() | PngSettings.t()) ::
Result.t(String.t(), binary())
@spec render(QR.t(), image_format(), SvgSettings.t() | PngSettings.t()) :: String.t()
def render(qr, :svg, settings) do
QRCode.Render.Svg.create(qr, settings)
end
Expand All @@ -68,21 +70,17 @@ defmodule QRCode.Render do
<img src="data:image/png; base64, encoded_png_qr_code" alt="QR code" />
```
"""
@spec to_base64(Result.t(String.t(), binary())) :: Result.t(String.t(), binary())
def to_base64({:ok, rendered_qr_matrix}) do
rendered_qr_matrix
|> Base.encode64()
|> Result.ok()
@spec to_base64(String.t()) :: String.t()
def to_base64(rendered_qr_matrix) do
Base.encode64(rendered_qr_matrix)
end

def to_base64(error), do: error

@doc """
Saves rendered QR code to `svg` or `png` file. See a few examples below:

```elixir
iex> "Hello World"
|> QRCode.create(:high)
|> QRCode.create!(:high)
|> QRCode.render(:svg)
|> QRCode.save("/path/to/hello.svg")
{:ok, "/path/to/hello.svg"}
Expand All @@ -91,21 +89,18 @@ defmodule QRCode.Render do
```elixir
iex> png_settings = %QRCode.Render.PngSettings{qrcode_color: {17, 170, 136}}
iex> "Hello World"
|> QRCode.create(:high)
|> QRCode.create!(:high)
|> QRCode.render(:png, png_settings)
|> QRCode.save("/tmp/to/hello.png")
{:ok, "/path/to/hello.png"}
```
![QR code color](docs/qrcode_color.png)
"""
@spec save(Result.t(any(), binary()), Path.t()) ::
Result.t(String.t() | File.posix() | :badarg | :terminated, Path.t())
def save({:ok, rendered_qr_matrix}, path_with_file_name) do
@spec save(String.t(), Path.t()) :: {:ok, Path.t()} | {:error, File.posix()}
def save(rendered_qr_matrix, path_with_file_name) do
case File.write(path_with_file_name, rendered_qr_matrix) do
:ok -> {:ok, path_with_file_name}
err -> err
end
end

def save(error, _path_with_file_name), do: error
end
10 changes: 3 additions & 7 deletions lib/qr_code/render/png.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@ defmodule QRCode.Render.Png do
@doc """
Create Png image from QR matrix as binary.
"""
@spec create(Result.t(String.t(), QR.t()), PngSettings.t()) :: Result.t(String.t(), binary())
def create({:ok, %QR{matrix: matrix}}, settings) do
matrix
|> create_png(settings)
|> Result.ok()
@spec create(QR.t(), PngSettings.t()) :: String.t()
def create(%QR{matrix: matrix}, settings) do
create_png(matrix, settings)
end

def create(error, _settings), do: error

# Private

defp create_png(
Expand Down
16 changes: 4 additions & 12 deletions lib/qr_code/render/svg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,15 @@ defmodule QRCode.Render.Svg do
Create Svg structure from QR matrix as binary. This binary contains svg
attributes and svg elements.
"""
@spec create(Result.t(String.t(), QR.t()), SvgSettings.t()) :: Result.t(String.t(), binary())
def create({:ok, %QR{matrix: matrix}}, settings) do
matrix
|> create_svg(settings)
|> Result.ok()
end

def create(error, _settings), do: error

# Private

defp create_svg(matrix, settings) do
@spec create(QR.t(), SvgSettings.t()) :: String.t()
def create(%QR{matrix: matrix}, settings) do
matrix
|> construct_body(%__MODULE__{}, settings)
|> construct_svg(settings)
end

# Private

defp construct_body(matrix, svg, %SvgSettings{scale: scale}) do
{rank_matrix, _} = Matrix.size(matrix)

Expand Down
34 changes: 12 additions & 22 deletions test/png_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule PngTest do
describe "Png" do
setup do
@text
|> QRCode.create()
|> QRCode.create!()
|> QRCode.render(:png)
|> QRCode.save(@dst_to_file)

Expand All @@ -20,22 +20,14 @@ defmodule PngTest do
end)
end

test "render should fail with error" do
rv =
Result.error("Error")
|> QRCode.render(:png)

assert rv == {:error, "Error"}
end

test "should save qr code to png file" do
assert File.exists?(@dst_to_file)
end

test "should create png from qr matrix" do
{:ok, expected} =
expected =
@text
|> QRCode.create()
|> QRCode.create!()
|> QRCode.render(:png)

rv =
Expand All @@ -46,35 +38,33 @@ defmodule PngTest do
end

test "should encode png binary to base64" do
{:ok, expected} =
rendered_qr =
@text
|> QRCode.create()
|> QRCode.create!()
|> QRCode.render(:png)

{:ok, rv} =
@text
|> QRCode.create()
|> QRCode.render(:png)
rv =
rendered_qr
|> QRCode.to_base64()
|> Result.and_then(&Base.decode64/1)
|> Base.decode64!()

assert expected == rv
assert rv == rendered_qr
end

test "file should contain different qr code color than black" do
expected =
@text
|> QRCode.create()
|> QRCode.create!()
|> QRCode.render(
:png,
%PngSettings{qrcode_color: {17, 170, 136}}
)

QRCode.save(expected, @dst_to_file)
{:ok, _} = QRCode.save(expected, @dst_to_file)

rv =
@dst_to_file
|> File.read()
|> File.read!()

assert expected == rv
end
Expand Down
Loading