ExApi - a library for creating and managing apis and their implementations

Hey @OvermindDL1, did you looked at it?
If so can you please let me know what you think about it (something not enough clear or maybe you see something missing)?

Do you have an idea how to dispatch my events (see &ExApi.notify_event/1 private function at bottom of main lib file) across nodes? I think about next release candidate for it. :slight_smile:

I’m also planning to create a fork of ex_doc (originally don’t plan it, but after I read original source code I can see that it’s easiest way to achieve what I’m looking for). I want to add ability to define custom modules and function groups for documentation, so for example in left sidebar you could see apis link that will work as same as modules or exceptions + each custom module could define how to group functions, so instead of: types, functions and callbacks you could see for example (on api implementation page): supported features, not supported features and not implemented features.

I think about something like:

defmodule Example do
  defmacro my_generator(arg1, ...) do
    # ...
    quote do
      # ...
      defmodule MyCustomModule do
        @module_type MyModuleType # i.e. I don't want a special fork only for ex_api
        # ...
      end
      # ...
    end
    # ...
  end
end
#  and finally
defmodule MyModuleType do
  def get_grouped_functions(module) do
    # get functions and group them
  end
  def group_label(group_name), do: # returns functions group name
  def label, do: "My custom modules" # return sidebar label
end

What do you think about it?

Looked over it a couple times but not made a project with it yet. I’m keeping it up in my active list to remind me to make a test project sometime to give it a try (still fairly busy at work). :slight_smile:

Make it hookable into different things? Phoenix.PubSub would be a nice default if it exists, else just load up a named receiving process on each node to receive the events and dispatch?

I’ve not actually delved into ex_doc as of yet, though @tmbb has a lot lately so he could probably give ideas on it. :slight_smile:

Sounds like a good PR idea though. :slight_smile:

1 Like

Ok, so wait please for next release candidate. :slight_smile:

I know that it was simple question. Just don’t used Phoenix.PubSub by myself yet (only used Phoenix that uses it),. Now I will remember it. Of course I already found docs and added support to project without bigger problem. I’m going to change some things in code, so I will not push next rc today, but in next days. I will ping you for notification. :slight_smile:

I remember his awesome work on Makeup. If he or anyone else have ideas or simple questions then I’m open to all of them and if needed I can also add additional informations to documentation.

I don’t understand exactly what you’re saying… You just want to group functions into groups that are not modules?

@tmbb: When you are writing macros to generate modules then you may would like to separate them from rest “normal” (plain) modules in your documentation to easier find them, so I want to group modules like now there is separated exceptions list. Similarly functions, I don’t expected declaring types in api and implementation, but I would like to separate supported features, not supported features and not implemented features. Right now there are all grouped in functions.

Lol, my time is starting to free up a touch at work so, soonish? ^.^

:thumbsup:

1 Like

Wonderful :smiley:

1 Like

I want to do small change in validation (+ update tests) and add ability to define fallback implementation - this implementation will not be registered with others and could be fetched in case there is no other implementation (unlike default implementation that is stored with other implementations).

1 Like

@Eiji You should provide an example project, a MVP so people like me, could understand it. :smiley:

3 Likes

@webdeb: I agree. I will definitely do lots of libraries/projects that will use this library, but I need to find a bigger time for it. Currently I’m focused on next release candidate.

2 Likes

Lol, my work on Makeup is not “awesome”. It’s literally just writing a grammar for 2 programming languages and steal the Pygments styles to render it :stuck_out_tongue:

The real merit lies in the ExSpirit library and the people who created the Pygments’ styles. Also important were the people who’ve written the Elixir lexer, because it gave me a useful baseline on what would be the least ampunt of parsing I’d have to do to get a decent output.

2 Likes

Yes it is! You spend your time and your result looks great and finally we can use results of your work for free. Who wants more?
:077:
Of course many thanks to all that made dependencies that we are using in our libraries, but anyway we are doing good work and it’s awesome! :smiley:

2 Likes

I think we can collect ideas, what is possible with this library at all.

1 Like

@ShalokShalom: In short it’s for defining apis of all 3rd-party apps, libraries and services.

I created this library to declare unique apis of multiple sources where each implementation could not support some part(s) of that api or is just partially implemented.

Also this library could be used as an alternative when you want to use Bahaviour and Protocol, but you don’t have a data that could be passed to each function - again see REST 3rd-party api services. Sometimes you prefer to write MyImpl.get_all() instead of MyImpl.get_all(%MyEmptyStruct{}) + you can dynamically list all implementations (which is not supported in plain Behaviour).

Another helpful thing is that your implementations are identified by {group, id} (both atoms), so you can group implementations and identify them simply by atom. Imagine that you want to create protocol that declares unique code hosting api for bitbucket, github, gitlab and other services. You could pass empty struct or struct with base url, but why do you need to pass anything when you want simply fetch all issues for github implementation?

1 Like

For all interested: I pushed new commit to master (not next rc yet).
This version has support for Phoenix.PubSub. I also updated validation part - they are used before calling or casting server + I added specs for all methods that used cast function. Now they returns: {:ok, server_pid} or {:error, error_struct} instead of :ok.
I’m want to add more things before next release candidate. I plan to release it tomorrow or in Monday.

1 Like

Just pushed new change to master. From now each api will use GenServer. Currently there is one async function feature_for/4.
This is introduced, because now you can call each feature asynchronously (using implementation api) or synchronously (using just implementation).

For example after you compile code like it:

def_api MyApi do
  @moduledoc "Example api"

  @doc "Do something really long"
  @spec feature_name(term) :: term
  def_feature feature_name(first_argument)

  def_impl :my_impl do
    def_feature_impl feature_name(first_argument) do
      Process.sleep(1000)
      "result: " <> to_string(first_argument)
    end
  end
end

# config for auto load:
use Mix.Config

config :ex_api, apis: %{MyApi => [MyApi.MyImpl]}

you can call feature_name asynchronously like:

# Note: no need to implement error callback if you don't expect and/or want catch errors
MyApi.feature_for({nil, :my_impl}, :feature_name, ["sample"], fn result -> IO.puts result end)

or you can also call it synchronously like:

# Note: if not specified group name is nil by default
{:ok, impl} = ExApi.get_impl(MyApi, {nil, :my_impl})
impl.module.feature_name("sample")
# or using module name:
MyApi.MyImpl.feature_name("sample")

I want to implement two more features (fallback implementation for api if no other implementations are registered and checking feature support, so you can firstly check support and then call feature). I plan to implement them tomorrow.

I just published second release candidate for ExApi!

ping: @OvermindDL1,

You can find docs here. Please let me know if something is still unclear.

1 Like

s/implmentations/implementations/ :slight_smile:

A question, is it suitable for a plugin system like, say, Firestorm forum or so? Where they could setup an ‘api’ with a set of hookpoints, and they call those hooks to have plugins do things like run some code or add something to the display or so? And if so, what would that look like? Say for these use-cases:

  1. Rendering a post so it calls the hook for that, one of the hooks wants to add a thumbs-up button to the bottom of the post, and another hook wants to take that post and change github links to contain inline content from the github link (so it needs to take the existing template and return a mutated version while all other hooks have their own chances to do the same).
  2. Hooking into when a post is saved so it can sanitize words or throw an error and another wants to take a post when it is saved so it can scan it for indexing purposes (it needs to take the post content and probably should return a mutated version of it that).

Things like that…

1 Like

Ooops, thanks :slight_smile:

Firstly, I need to say that I don’t wrote any forums and plug-ins for them, so maybe someone could correct me. Here is one possible solution:

# in your code
import ExApi.Api

def_api Firestorm.Hooks.Api do
  @moduledoc "Hooks API for Firestorm forum."

  # @doc "Hook description goes here ..."
  # @spec first_hook_name(...)
  def_feature first_hook_name(arg1, arg2, ...)

  # other feature implementations, declare no support or simply don't implement
end

# in plug-in code:
import ExApi.Implementation

def_api_impl Firestorm.Hooks.Api, :overmind_dl1_hooks, OvermindDL1.Firestorm.HooksImplementation do
  def_feature_impl first_hook_name(arg1, arg2, ...) do
    # hook implementation
  end

  # other feature definitions
end

# main hooks module:
defmodule Firestorm.Hooks do
  alias Firesotrm.Hooks.Api

  def call(name, args, finish_callback, failure_callback) when is_atom(name) and is_list(args) and is_function(finish_callback) and is_function(failure_callback) do
    func_call = {name, Enum.count(args} # {name, arity}
    {:ok, hooks_api_impls} = ExApi.get_impls(Api)
    # here `hooks_api_impls` is a map %{group_name => impls}
    # you could get hooks for specified group
    # or get map values and flatten result
    # for example, you want to declare `def core_call(name, args)`
    # and fetch impls like: `hooks_api_impls[:firestorm_core]`
    choosed_hooks = # ...
    for impl <- choosed_hooks, is_supported_hook(impl, func_call) do
      # here you could call hook asynchronously like:
      {:ok, _pid} = impl.api.feature_for(impl, name, args, &finish_callback/1, &failure_callback/1)
      # or synchronously like:
      case apply(impl.module, name, args) do
        {:ok, result} -> apply(&finish_callback(result))
        {:error, error} -> apply(&failure_callback(result))
      end
    end
  end

  defp is_supported_hook(impl, func_call) do
    {:ok, result} = ExApi.get_feature_support(impl, func_call)
  end
end

Of course you need to implement something like Firesotrm.Hooks.Loader.

Hmm, how about that these hooks will use drab library?

Hmm, my idea is to do something like:

defmodule Firestorm.Article do
  @moduledoc "Article context"

  alias Firestorm.Article.Post
  alias Firestorm.{Hooks, Repo}

  @doc "Validates, call hooks and saves post data into database"
  def save_post(input_data) do
    changeset = Post.changeset(input_data)
    Hooks.call(:before_save_post, [changeset], &on_before_save_post_finish/1, &on_before_save_post_failure/1)
  end

  defp on_before_save_post_failure(error) do
    # format error and show user, auto-submit bug, or just do nothing :-)
  end

  defp on_before_save_post_finish(result) do
    Repo.insert(result)
  end

  # other context functions ...
end

I wrote this response quickly so there could be errors/mistakes/typos, but this answer firstly comes to my mind. Let me think what do you think about it.

2 Likes

Well that looks awesome! That is what I was most curious about, an example I could digest to see how it works (I need to dedicate some time to read your extensive thoughts, I keep getting distracted at work so cannot keep my mind on them long enough for it to soak in ^.^;).

Looks quite cool! :slight_smile:

2 Likes