Iex.new

Iex.new

Looking for code review for my first Library!

Hello !

As project to start to learn Elixir I have decided to begin with a small library.
This library has for goal to supply functions to help to communicate with the API of https://carbone.io
Carbone.io produce documents in different formats based on a (json) template (language) and the data supplied in the same format.

“Design” choices:

  • http client : Finch
  • system env-variable must provide the API-Token for the authentication
  • system env-variable can provide the base URI of the the Service
  • system env-variable can provide the version of the service to use

As beginner I’m unsure if Finch is good fit for the library instead of Tesla

Units tests:

I have started to write a few tests and I’m not sure how to test the following functions because they call Finch functions:

  • create_document/3
  • add_template/2
  • render_template/2

I would really appreciate if you can review my code and also give me some advices how to improve it.

The Elixir code for the library is available on my Github repository.

An example of “Carbone.io” template can be found here .

Thanks a lot ! :smiley:

Most Liked

evadne

evadne

Hi, sorry for adding to such a long thread

[1]

I do not like APIs that require string keys / other than camel case as input, such as the request-body using convertTo, for example

request_body = %{"convertTo" => "pdf", "data" => data}
Carbonex.create(finch_name, "path/to/template.odt", request_body)

should ideally be

Carbonex.create(template_path, data, convert_to: :pdf)

In general if you read Elixir source code a lot you will find that folks usually use camel case only, we do not do title case much, so it would be good to make it consistent.

Also exposing Finch as your HTTP API is probably not ideal (where does finch_name come from, etc) better to make it configurable if you have to but otherwise have a default API that does not require plugging this information.

Case in hand ExAws uses hackney but doesn’t ask you what pool, it just uses the :ex_aws pool.

[2]

README needs work, show me how to actually use the library and I don’t want to see stuff that is standard such as “Documentation can be generated with ExDoc and published on HexDocs” which is just part of the original boilerplate.

The README should describe what is available in the library and how to use it. If I copy and paste each chunk of code into iex it should execute, which it does not right now. Also it would seem that carbone.io would require an API key, but the way to configure it is unclear. This must be addressed.

I am not to impose a rigid structure, but it is my opinion that for any library, the README should include the following:

  1. Name of the library
  2. Succinct description of the problem it solves (do not write a nerd essay, keep it under 100 words)
  3. If the library solves a common problem, explanation of the problem and analysis of all equivalent solutions, justification for using this library over others (this can be skipped if writing first-party platform SDKs, otherwise especially if the library is algorithmic or data-intensive, it would be good to add some benchmark results)
  4. Installation & configuration (the “add this to mix.exs” section comes in here, also any environment variables & key configuration should be included, once I’ve read this section I should have everything needed to get the library integrated)
  5. Some examples / usage (can be part of hexdocs instead)

And if you are nice then add changelogs, acknowledgements, reference, etc.

Link to hexdocs should be possible to find within the first 1 second of looking at the README.

[3]

For symmetry reasons, you might want to instead call the function Carbonex.render vs Carbonex.create the latter has no direct counterpart in the API of the service. In general when writing an Elixir SDK for a service you should match the name of whatever method/function published by the service.

So I would expect something like this

@type option :: {:convert_to, :pdf}
@type data :: binary() | map()
@spec render(template_name :: string(), data :: data(), options :: list(option())) ::
  {:ok, document_data :: binary()} | {:error, reason :: term()}

Note how we are typing the option as a 2-arity tuple, this is so the options can be passed in as a Keyword and its type will be checked. If you write it as a map it will be messier.

You can check out Stripe and how they intersperse different SDKs together alongside with cURL, the bottom line here is that integration engineers are very busy and you need to make sure your Elixir SDK is aligned with the service as much as possible.

[4]

carbonex/templates/Sample_template.odt

Wrong capitalisation, should be

carbonex/templates/sample_template.odt

Sweat the small stuff

[5]

See Carbonex requires CARBONE_TOKEN to be set in the environment, I would think that this is an Elixir anti-pattern and folks would be happier if you wrapped the configuration in a context object etc.

Read Library guidelines — Elixir v1.21.0-dev for a bit and again compare how ex_aws is configured… Will users want to use more than 1 token with your library? If no then you can keep what you have, or change it so you get it from Config.

Otherwise, you have to do something like this

defmodule Carbonex.Environment do
  @enforce_keys ~w(token)a
  defstruct token: nil
end

defmodule Carbonex do
  def render(environment, data, options) do
    …
  end
end

Which will be used thusly

environment = Carbonex.Environment{token: "…"}
Carbonex.render(environment, …)

Again use own judgement

[6]

Lastly from a maintainability point of view, does your platform publish OpenAPI v3 Specs? If so, consider code generation vs hand-rolling code

BartOtten

BartOtten

Nice review! One thing to note is that piping into a case has been described as a bad pattern. The suggested solution is to extract the cases into a function (with multiple heads).

Benefits:

  1. Function name can be explanatory about what’s happening
  2. Easier to test the ‘cases’
hst337

hst337

Hi, it’s a nice code for the first project. Here are the things I’ve noted during reading

Not important things

  • mix format. Use spaces or tabs, not both

  • Translate this

              response
              |> get_template_id_from_response
    

    to get_template_id_from_response(response)

  • Do not refer to the functions in module like Carbonex.function, use just function.

  • Translate

      case decode_json(response) do
        {:ok, map} -> map["data"]["templateId"]
        _ -> nil
      end
    

    to

      case decode_json(response) do
        {:ok, %{"data" => %{"templateId" => id}}} -> id
        _ -> nil
      end
    

    By the way, the code above occurs twice, so I’d suggest to DRY it.

  • You can pipe into case, so instead

    result = Jason.encode(data)
    case result do
    

    you can write

    data
    |> Jason.encode()
    |> case do
    

Important things and design

  • More tests. To test the HTTP interactions, you can use awesome Bypass library.

  • Do not use System.get_env. I’d suggest just to create some structure which holds url, version and token for the carbone.io. This will make the code more pure (in functional sense) and easier to test

  • Provide function which starts finch with given url, version and token.

  • Learn specs. Proper and precise type annotations are better than any documentation.

  • I think that get_document function should return the document, but not the HTTP response structure.

  • create_multipart function checks the existence of a file, while it has already been checked in add_template function

Last Post!

cjbottaro

cjbottaro

Yeah, one of the pros is static analysis (Dialyzer) of Elixir functions. :+1:

Where Next?

Popular in Discussions Top

CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
pillaiindu
In django there is a cache framework backed by memcached. Rails also puts a lot of emphasis on caching, and even the idea of russian-doll...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
matthias_toepp
I’d love to hear what people think about Wisp, the new Gleam web framework started by Gleam’s primary creator Louis Pilfold. Gleam, alon...
New

Other popular topics Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54260 488
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New

We're in Beta

About us Mission Statement