README.md

<!-- markdownlint-disable MD033 -->
<!-- x-hide-in-docs-start -->
<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/open-feature/community/0e23508c163a6a1ac8c0ced3e4bd78faafe627c7/assets/logo/horizontal/white/openfeature-horizontal-white.svg" />
    <img align="center" alt="OpenFeature Logo" src="https://raw.githubusercontent.com/open-feature/community/0e23508c163a6a1ac8c0ced3e4bd78faafe627c7/assets/logo/horizontal/black/openfeature-horizontal-black.svg" />
  </picture>
</p>

<h2 align="center">OpenFeature Elixir SDK</h2>

<!-- x-hide-in-docs-end -->
<!-- The 'github-badges' class is used in the docs -->
<p align="center" class="github-badges">
  <a href="https://github.com/open-feature/spec/releases/tag/v0.7.0">
    <img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.7.0&color=yellow&style=for-the-badge" />
  </a>
  <!-- x-release-please-start-version -->

  <a href="https://github.com/open-feature/elixir-sdk/releases/tag/v0.1.1">
    <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.1.1&color=blue&style=for-the-badge" />
  </a>

  <!-- x-release-please-end -->
  <br/>
  <a href="https://hexdocs.pm/open_feature/">
    <img alt="Docs" src="https://img.shields.io/badge/docs-hexpm-blue.svg" />
  </a>
  <a href="https://github.com/open-feature/elixir-sdk/blob/main/LICENSE.md">
    <img alt="License" src="https://img.shields.io/hexpm/l/req.svg" />
  </a>
  <a href="https://github.com/open-feature/elixir-sdk/actions/workflows/ci.yml">
    <img alt="CI" src="https://github.com/open-feature/elixir-sdk/actions/workflows/ci.yml/badge.svg" />
  </a>
  <a href="https://bestpractices.coreinfrastructure.org/projects/6601">
    <img alt="CII Best Practices" src="https://bestpractices.coreinfrastructure.org/projects/6601/badge" />
  </a>
</p>
<!-- x-hide-in-docs-start -->

[OpenFeature](https://openfeature.dev) is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool or in-house solution.

<!-- x-hide-in-docs-end -->
## 🚀 Quick start

### Requirements

It requires Elixir 1.14 or greater to run.

### Install

The package can be installed by adding `open_feature` to your list of dependencies
in `mix.exs`:

```elixir
def deps do
  [{:open_feature, "~> 0.1"}]
end
```

### Usage

```elixir
provider = %OpenFeature.Provider.InMemory{
  flags: %{
    "v2_enabled" => %{
      disabled: false,
      default_variant: "on",
      variants: %{
        "on" => true,
        "off" => false,
      }
    }
  }
}
{:ok, provider} = OpenFeature.set_provider(provider)
client = OpenFeature.get_client()
v2_enabled = OpenFeature.Client.get_boolean_value(client, "v2_enabled", false)
```

### API Reference

For details, including API documentation, see the respective [Hex docs](https://hexdocs.pm/open_feature/).

## 🌟 Features

| Status | Features                                                            | Description                                                                                                                                                  |
| ------ | --------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ⚠️      | [Providers](#providers)                                             | Integrate with a commercial, open source, or in-house feature management tool.                                                                               |
| ✅      | [Targeting](#targeting)                                             | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context).                           |
| ✅      | [Hooks](#hooks)                                                     | Add functionality to various stages of the flag evaluation life-cycle.                                                                                       |
| ✅      | [Logging](#logging)                                                 | Integrate with popular logging packages.                                                                                                                     |
| ✅      | [Domains](#domains)                                                 | Logically bind clients with providers.                                                                                                                       |
| ✅      | [Eventing](#eventing)                                               | React to state changes in the provider or flag management system.                                                                                            |
| ✅      | [Shutdown](#shutdown)                                               | Gracefully clean up a provider during application shutdown.                                                                                                  |
| ✅      | [Extending](#extending)                                             | Extend OpenFeature with custom providers.                                                                                                          |

<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>

### Providers

[Providers](https://openfeature.dev/docs/reference/concepts/provider) are an abstraction between a flag management system and the OpenFeature SDK.
Look [here](https://openfeature.dev/ecosystem?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Elixir) for a complete list of available providers.
If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself.

Once you've added a provider as a dependency, it can be registered with OpenFeature like this:

```elixir
provider = %OpenFeature.Provider.InMemory{
  flags: %{
    "v2_enabled" => %{
      disabled: false,
      default_variant: "one",
      variants: %{
        "on" => true,
        "off" => false
      }
    }
  }
}
{:ok, provider} = OpenFeature.set_provider(provider)
```

In some situations, it may be beneficial to register multiple providers in the same application.
This is possible using [domains](#domain), which is covered in more detail below.

### Targeting

Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location.
In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary#targeting).
If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context).

```elixir
# set a value to the global context
OpenFeature.set_global_context(%{region: "us-east-1"})

# set a value to the client context
client = OpenFeature.get_client() |> OpenFeature.Client.set_context(%{region: "us-east-1"})

# set a value to the invocation context
flag_value = OpenFeature.Client.get_boolean_value(client, "some-flag", flag, %{region: "us-east-1"})
```

### Hooks

[Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle.
Look [here](https://openfeature.dev/ecosystem/?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Hook&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Elixir) for a complete list of available hooks.

Once you've added a hook as a dependency, it can be registered at the client or flag invocation level.

```elixir
## add a hook on this client, to run on all evaluations made by this client
client = OpenFeature.Client.add_hooks(client, [%OpenFeature.Hook{}])

## add a hook for this evaluation only
flag_value = OpenFeature.Client.get_boolean_value(client, flag_key, false, hooks: [%OpenFeature.Hook{}]);
```

### Logging

The Elixir SDK uses the default Elixir Logger.

### Domains

Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider.
If a domain has no associated provider, the default provider is used.

```elixir
provider = %OpenFeature.Provider.InMemory{
  flags: %{
    "v2_enabled" => %{
      disabled: false,
      default_variant: "default",
      variants: %{
        "default" => true
      }
    }
  }
}

# registering the default provider
{:ok, _provider} = OpenFeature.set_provider(provider)
# registering a provider to a domain
{:ok, _provider} = OpenFeature.set_provider("my-domain", provider)

# A client bound to the default provider
default_client = OpenFeature.get_client()
# A client bound to the CachedProvider provider
domain_client = OpenFeature.get_client("my-domain")
```

### Eventing

Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions.
Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider.
Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`.

Please refer to the documentation of the provider you're using to see what events are supported.

```elixir
# add an event handler to a client
OpenFeature.Client.add_event_handler(client, :provider_configuration_changed, fn event_details ->
  # do something when the provider's flag settings change
end)
```

### Shutdown

The OpenFeature API provides a close function to perform a cleanup of all registered providers.
This should only be called when your application is in the process of shutting down.

```elixir
OpenFeature.shutdown()
```

## Extending

### Develop a provider

To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency.
This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/elixir-sdk-contrib) available under the OpenFeature organization.
You’ll then need to write the provider by implementing the `OpenFeature.Provider` behaviour exported by the OpenFeature SDK.

```elixir
defmodule OpenFeature.Provider.NoOp do
  alias OpenFeature.ResolutionDetails

  @behaviour OpenFeature.Provider

  defstruct name: "NoOp", domain: nil, state: :not_ready, hooks: []

  def initialize(provider, domain, _evaluation_context), do: {:ok, %{provider | state: :ready, domain: domain}}
  def shutdown(_provider), do: :ok

  def resolve_boolean_value(_provider, _key, default, _context), do: {:ok, %ResolutionDetails{value: default}}
  def resolve_string_value(_provider, _key, default, _context), do: {:ok, %ResolutionDetails{value: default}}
  def resolve_number_value(_provider, _key, default, _context), do: {:ok, %ResolutionDetails{value: default}}
  def resolve_map_value(_provider, _key, default, _context), do: {:ok, %ResolutionDetails{value: default}}
end

```

> Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs!

<!-- x-hide-in-docs-start -->
## ⭐️ Support the project

- Give this repo a ⭐️!
- Follow us on social media:
  - Twitter: [@openfeature](https://twitter.com/openfeature)
  - LinkedIn: [OpenFeature](https://www.linkedin.com/company/openfeature/)
- Join us on [Slack](https://cloud-native.slack.com/archives/C0344AANLA1)
- For more, check out our [community page](https://openfeature.dev/community/)

## 🤝 Contributing

Interested in contributing? Great, we'd love your help! To get started, take a look at the [CONTRIBUTING](CONTRIBUTING.md) guide.

### Thanks to everyone who has already contributed

<a href="https://github.com/open-feature/elixir-sdk/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=open-feature/elixir-sdk" alt="Pictures of the folks who have contributed to the project" />
</a>


Made with [contrib.rocks](https://contrib.rocks).
<!-- x-hide-in-docs-end -->