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

Improve docs and type specs #3

Merged
merged 5 commits into from
Jul 2, 2023
Merged
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
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"},
}
Loading