Skip to content

Commit

Permalink
Improve docs and type specs (#3)
Browse files Browse the repository at this point in the history
* Modernize test workflow

* Update docs

* Add is_uuid guard

* Add custom types

* Bump to 2.1.1
  • Loading branch information
ream88 authored Jul 2, 2023
1 parent 7795322 commit f8467d1
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 53 deletions.
56 changes: 29 additions & 27 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,47 @@ on:
pull_request:
push:
branches:
- master
- main
repository_dispatch:

jobs:
test:
runs-on: ubuntu-16.04
env:
MIX_ENV: test
strategy:
fail-fast: false
matrix:
include:
- pair:
elixir: 1.7.x
otp: 19.x
- pair:
elixir: 1.11.x
otp: 23.x
lint: lint
- elixir: 1.7
otp: 20
runs-on: ubuntu-20.04
- elixir: 1.11
otp: 23
runs-on: ubuntu-20.04
- elixir: 1.15
otp: 26
runs-on: ubuntu-22.04
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v2

- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.pair.otp}}
elixir-version: ${{matrix.pair.elixir}}

- name: Install Dependencies
run: mix deps.get --only test

otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- uses: actions/cache/restore@v3
with:
path: deps
key: ${{ matrix.elixir }}-mix-${{ hashFiles('/mix.lock') }}
- run: mix deps.get
- uses: actions/cache/save@v3
with:
path: deps
key: ${{ matrix.elixir }}-mix-${{ hashFiles('/mix.lock') }}
- run: mix deps.unlock --check-unused
if: ${{ matrix.elixir != 1.7 }}
- run: mix format --check-formatted
if: ${{ matrix.lint }}

- run: mix deps.get && mix deps.unlock --check-unused
if: ${{ matrix.lint }}

- run: mix deps.compile

if: ${{ matrix.elixir != 1.7 }}
- run: mix compile --warnings-as-errors
if: ${{ matrix.lint }}

- run: mix test
if: ${{ matrix.elixir != 1.15 }}
- run: mix test --warnings-as-errors
if: ${{ matrix.elixir == 1.15 }}
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

<!-- MDOC !-->

ShortUUID allows UUIDs to be encoded in [a more URL- and user-friendly Base58
format](https://github.com/bitcoin/bitcoin/blob/08a7316c144f9f2516db8fa62400893f4358c5ae/src/base58.h#L6-L13):
An Elixir module for handling ShortUUIDs.

This module provides functions to encode and decode between regular UUIDs and ShortUUIDs.

It can be especially useful when you want to use UUIDs, but you need them to be shorter.
For example, you can use ShortUUIDs in URL shorteners or for a more user-friendly representation of UUIDs.

Inspired by [bitcoin](https://github.com/bitcoin/bitcoin/blob/08a7316c144f9f2516db8fa62400893f4358c5ae/src/base58.h#L6-L13).

## Examples

```elixir
iex> ShortUUID.encode("64d7280f-736a-4ffa-b9c0-383f43486d0b")
Expand Down
58 changes: 42 additions & 16 deletions lib/short_uuid.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule ShortUUID do
import ShortUUID.Guards

@external_resource "README.md"

@moduledoc "README.md"
Expand All @@ -15,9 +17,27 @@ defmodule ShortUUID do

@abc_length Enum.count(@abc)

@typedoc """
A UUID (Universally Unique Identifier) is a 128-bit number used to uniquely identify
some object or entity on the internet. In its canonical form, a UUID is represented
by 32 lowercase hexadecimal digits, displayed in five groups separated by hyphens,
in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric characters and 4 hyphens).
"""
@type uuid :: binary()

@typedoc """
A short UUID is a more compact string representation of a UUID. It encodes the
exact same information but uses a larger character set. This results in a shorter
string for the same information.
"""
@type short_uuid :: binary()

@doc """
Encodes the given UUID into a ShortUUID.
The function returns a tuple `{:ok, short_uuid}` if encoding is successful. In
case of invalid input UUID, the function returns `{:error, :invalid_uuid}`.
## Examples
iex> ShortUUID.encode("64d7280f-736a-4ffa-b9c0-383f43486d0b")
Expand All @@ -27,20 +47,16 @@ defmodule ShortUUID do
{:error, :invalid_uuid}
"""
@spec encode(String.t()) :: {:ok, String.t()} | {:error, :invalid_uuid}
def encode(input) when is_binary(input) do
case input do
<<_a::64, ?-, _b::32, ?-, _c::32, ?-, _d::32, ?-, _e::96>> ->
input
|> String.replace("-", "")
|> String.to_integer(16)
|> encode("")

_ ->
{:error, :invalid_uuid}
end
@spec encode(uuid()) :: {:ok, short_uuid()} | {:error, :invalid_uuid}
def encode(input) when is_uuid(input) do
input
|> String.replace("-", "")
|> String.to_integer(16)
|> encode("")
end

def encode(_), do: {:error, :invalid_uuid}

defp encode(input, output) when input > 0 do
index = rem(input, @abc_length)
input = div(input, @abc_length)
Expand All @@ -52,7 +68,10 @@ defmodule ShortUUID do
defp encode(0, output), do: {:ok, output}

@doc """
Encodes the given UUID into a ShortUUID. Raises in case of any errors.
Encodes the given UUID into a ShortUUID.
This function works similarly to `encode/1`, but instead of returning an error
tuple, it raises `ArgumentError` in case of an invalid UUID.
## Examples
Expand All @@ -61,7 +80,7 @@ defmodule ShortUUID do
"""
@doc since: "2.1.0"
@spec encode!(String.t()) :: String.t()
@spec encode!(uuid()) :: short_uuid()
def encode!(input) do
case encode(input) do
{:ok, result} -> result
Expand All @@ -72,6 +91,9 @@ defmodule ShortUUID do
@doc """
Decodes the given ShortUUID back into a UUID.
The function returns a tuple `{:ok, uuid}` if decoding is successful. In case
of invalid input ShortUUID, the function returns `{:error, :invalid_uuid}`.
## Examples
iex> ShortUUID.decode("DTEETeS5R2XxjrVTZxXoJS")
Expand All @@ -84,7 +106,7 @@ defmodule ShortUUID do
{:error, :invalid_uuid}
"""
@spec decode(String.t()) :: {:ok, String.t()} | {:error, :invalid_uuid}
@spec decode(short_uuid()) :: {:ok, uuid()} | {:error, :invalid_uuid}
def decode(input) when is_binary(input) do
codepoints = String.codepoints(input)

Expand All @@ -103,7 +125,10 @@ defmodule ShortUUID do
end

@doc """
Decodes the given ShortUUID back into a UUID. Raises in case of any errors.
Decodes the given ShortUUID back into a UUID.
This function works similarly to `decode/1`, but instead of returning an error
tuple, it raises `ArgumentError` in case of an invalid ShortUUID.
## Examples
Expand All @@ -112,6 +137,7 @@ defmodule ShortUUID do
"""
@doc since: "2.1.0"
@spec decode!(short_uuid()) :: uuid()
def decode!(input) do
case decode(input) do
{:ok, result} -> result
Expand Down
12 changes: 12 additions & 0 deletions lib/short_uuid/guards.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule ShortUUID.Guards do
@doc """
Checks if the input is a valid UUID string.
"""
defguard is_uuid(value)
when is_binary(value) and
byte_size(value) == 36 and
binary_part(value, 8, 1) == "-" and
binary_part(value, 13, 1) == "-" and
binary_part(value, 18, 1) == "-" and
binary_part(value, 23, 1) == "-"
end
10 changes: 7 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ShortUUID.MixProject do
use Mix.Project

@version "2.1.0"
@version "2.1.1"
@url "https://github.com/YodelTalk/short_uuid"
@maintainers ["Mario Uher"]

Expand All @@ -18,7 +18,11 @@ defmodule ShortUUID.MixProject do
# Docs
name: "ShortUUID",
source_url: @url,
docs: [main: "ShortUUID"]
docs: [
main: "ShortUUID",
api_reference: false,
extras: ["README.md", "LICENSE"]
]
]
end

Expand All @@ -44,7 +48,7 @@ defmodule ShortUUID.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:ex_doc, "~> 0.23.0", only: :dev, runtime: false},
{:ex_doc, "~> 0.29.4", only: :dev, runtime: false},
{:stream_data, "~> 0.5.0", only: :test}
]
end
Expand Down
11 changes: 6 additions & 5 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"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"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"},
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
}

0 comments on commit f8467d1

Please sign in to comment.