Skip to content

Commit

Permalink
Turn embed into a behaviour (#201)
Browse files Browse the repository at this point in the history
* Turn embed into a behaviour

* Adjust type specs

* defmacro

* consistency

* Fix wording

* Add type check for callbacks

* handle nil

* add example in documentation and tests

Co-authored-by: Awlex <no thank you>
  • Loading branch information
Awlexus committed Jan 4, 2021
1 parent 702d63a commit f76a5e9
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
134 changes: 134 additions & 0 deletions lib/nostrum/struct/embed.ex
Expand Up @@ -31,6 +31,49 @@ defmodule Nostrum.Struct.Embed do
]
}
```
## Using structs
You can also create `Nostrum.Struct.Embed`s from structs, by using the
`Nostrum.Struct.Embed` module. Here's how the example above could be build using structs
```Elixir
defmodule MyApp.MyStruct do
use Nostrum.Struct.Embed
defstruct []
def title(_), do: "craig"
def description(_), do: "nostrum"
def url(_), do: "https://google.com/"
def timestamp(_), do: "2016-05-05T21:04:13.203Z"
def color(_), do: 431_948
def fields(_) do
[
%Nostrum.Struct.Embed.Field{name: "Field 1", value: "Test"},
%Nostrum.Struct.Embed.Field{name: "Field 2", value: "More test", inline: true}
]
end
end
iex> Nostrum.Struct.Embed.from(%MyApp.MyStruct{})
%Nostrum.Struct.Embed{
title: "craig",
description: "nostrum",
url: "https://google.com/",
timestamp: "2016-05-05T21:04:13.203Z",
color: 431_948,
fields: [
%Nostrum.Struct.Embed.Field{name: "Field 1", value: "Test"},
%Nostrum.Struct.Embed.Field{name: "Field 2", value: "More test", inline: true}
]
}
```
See this modules callbacks for a list of all the functions that can be implemented.
The implementation of these callbacks is optional. Not implemented functions will simply
be ignored.
"""

alias Nostrum.Struct.Embed.{Author, Field, Footer, Image, Provider, Thumbnail, Video}
Expand Down Expand Up @@ -118,6 +161,47 @@ defmodule Nostrum.Struct.Embed do
fields: fields
}

@callback author(struct) :: author()
@callback color(struct) :: integer() | nil
@callback fields(struct) :: fields()
@callback description(struct) :: description()
@callback footer(struct) :: footer()
@callback image(struct) :: url()
@callback thumbnail(struct) :: url()
@callback timestamp(struct) :: timestamp()
@callback title(struct) :: title()
@callback url(struct) :: url()

defmacro __using__(_) do
quote do
@behaviour Nostrum.Struct.Embed

def author(_), do: nil
def color(_), do: nil
def fields(_), do: nil
def description(_), do: nil
def footer(_), do: nil
def image(_), do: nil
def thumbnail(_), do: nil
def timestamp(_), do: nil
def title(_), do: nil
def url(_), do: nil

defoverridable(
author: 1,
color: 1,
fields: 1,
description: 1,
footer: 1,
image: 1,
thumbnail: 1,
timestamp: 1,
title: 1,
url: 1
)
end
end

@doc ~S"""
Puts the given `value` under `:title` in `embed`.
Expand Down Expand Up @@ -255,6 +339,10 @@ defmodule Nostrum.Struct.Embed do
```
"""
@spec put_image(t, Image.url()) :: t
def put_image(%__MODULE__{} = embed, nil) do
%__MODULE__{embed | image: nil}
end

def put_image(%__MODULE__{} = embed, url) do
image = %Image{
url: url
Expand All @@ -279,6 +367,10 @@ defmodule Nostrum.Struct.Embed do
```
"""
@spec put_thumbnail(t, Thumbnail.url()) :: t
def put_thumbnail(%__MODULE__{} = embed, nil) do
%__MODULE__{embed | thumbnail: nil}
end

def put_thumbnail(%__MODULE__{} = embed, url) do
thumbnail = %Thumbnail{
url: url
Expand Down Expand Up @@ -381,6 +473,48 @@ defmodule Nostrum.Struct.Embed do
put_field(%__MODULE__{embed | fields: []}, name, value, inline)
end

@doc """
Create an embed from a struct that implements the `Nostrum.Struct.Embed` behaviour
"""
def from(%module{} = struct) do
# checks if the struct implements the behaviour
unless Enum.member?(module.module_info(:attributes), {:behaviour, [__MODULE__]}) do
raise "#{module} does not implement the behaviour #{__MODULE__}"
end

embed =
%__MODULE__{}
|> put_color(module.color(struct))
|> put_description(module.description(struct))
|> put_image(module.image(struct))
|> put_thumbnail(module.thumbnail(struct))
|> put_timestamp(module.timestamp(struct))
|> put_title(module.title(struct))
|> put_url(module.url(struct))

embed =
case module.author(struct) do
%Author{} = author -> put_author(embed, author.name, author.url, author.icon_url)
nil -> embed
other -> raise "\"#{inspect(other)}\" is invalid for type author()"
end

embed =
case module.footer(struct) do
%Footer{} = footer -> put_footer(embed, footer.text, footer.icon_url)
nil -> embed
other -> raise "\"#{inspect(other)}\" is invalid for type footer()"
end

struct
|> module.fields()
|> List.wrap()
|> Enum.reduce(embed, fn
%Field{} = field, embed -> put_field(embed, field.name, field.value, field.inline)
other, _ -> raise "\"#{inspect(other)}\" is invalid for type fields()"
end)
end

# TODO: Jump down the rabbit hole
@doc false
def p_encode do
Expand Down
18 changes: 18 additions & 0 deletions test/example_embed.ex
@@ -0,0 +1,18 @@
defmodule MyApp.MyStruct do
use Nostrum.Struct.Embed

defstruct []

def title(_), do: "craig"
def description(_), do: "nostrum"
def url(_), do: "https://google.com/"
def timestamp(_), do: "2016-05-05T21:04:13.203Z"
def color(_), do: 431_948

def fields(_) do
[
%Nostrum.Struct.Embed.Field{name: "Field 1", value: "Test"},
%Nostrum.Struct.Embed.Field{name: "Field 2", value: "More test", inline: true}
]
end
end

0 comments on commit f76a5e9

Please sign in to comment.