diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dbc949..dee09d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,17 +3,18 @@ on: push jobs: test: runs-on: ubuntu-20.04 - name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} + name: OTP ${{matrix.versions.otp}} / Elixir ${{matrix.versions.elixir}} strategy: matrix: - otp: ['26.2.5'] - elixir: ['1.17.0'] + # Minimum and maximum supported versions + versions: [{ elixir: '1.14.0', otp: '25' }, { elixir: '1.18.0', otp: '26.2.5' }] steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: - otp-version: ${{matrix.otp}} - elixir-version: ${{matrix.elixir}} + otp-version: ${{matrix.versions.otp}} + elixir-version: ${{matrix.versions.elixir}} + - run: mix deps.unlock --all # compiles and runs tests against latest versions of dependencies - run: mix deps.get - run: mix test - run: mix dialyzer --format github diff --git a/CHANGES.txt b/CHANGES.txt index 80d48e7..eaff78b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,11 +1,11 @@ -0.2.0 (February XX, 2025): +0.2.0 (February 14, 2025): - Added new variations of the get treatment functions to support evaluating flags in given flag set/s: `Split.get_treatments_by_flag_set/3`, `Split.get_treatments_by_flag_sets/3`, `Split.get_treatments_with_config_by_flag_set/3`, and `Split.get_treatments_with_config_by_flag_sets/3`. - BREAKING CHANGES: - Removed the `fallback_enabled` option from `Split.Supervisor.start_link/1`. Fallback behavior is now always enabled, so `Split` functions no longer return `{:error, _}` tuples but instead use the fallback value when an error occurs. - Renamed the `Split.Treatment` struct to `Split.TreatmentWithConfig` and removed the `label`, `change_number`, and `timestamp` fields. - Moved the `Split` struct to the new `Split.SplitView` module and updated some fields: renamed `configurations` to `configs`, `flag_sets` to `sets`, and added the `impressions_disabled` field. - Updated the return types of `Split.get_treatment/3` and `Split.get_treatments/3` to return a treatment string and a map of treatment strings, respectively. - - Updated all `get_treatment` function signatures: removed the third argument (`bucketing_key`) and expanded the first argument (`key`) to accept a union, allowing either a string or a map with a key and optional bucketing key (`{:matching_key, String.t(), :bucketing_key, String.t() | nil}`). + - Updated all `get_treatment` function signatures: removed the third argument (`bucketing_key`) and expanded the first argument (`key`) to accept a union, allowing either a string or a map with a key and optional bucketing key (`%{required(:matchingKey) => String.t(), optional(:bucketingKey) => String.t() | nil}`). 0.1.0 (January 27, 2025): - BREAKING CHANGES: diff --git a/CONTRIBUTORS-GUIDE.md b/CONTRIBUTORS-GUIDE.md index 0f014b8..df52810 100644 --- a/CONTRIBUTORS-GUIDE.md +++ b/CONTRIBUTORS-GUIDE.md @@ -9,13 +9,14 @@ Split SDK is an open source project and we welcome feedback and contribution. Th 3. While developing, use descriptive messages in your commits. Avoid short or meaningless sentences like: "fix bug". 4. Make sure to add tests for both positive and negative cases. 5. If your changes have any impact on the public API, make sure you update the type specification and documentation attributes (`@spec`, `@doc`, `@moduledoc`), as well as it's related test file. -6. Run the build script (`mix compile`) and the static type analysis (`mix dialyzer`) and make sure it runs with no errors. -7. Run all tests (`mix test`) and make sure there are no failures. -8. `git push` your changes to GitHub within your topic branch. -9. Open a Pull Request(PR) from your forked repo and into the `development` branch of the original repository. -10. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project. -11. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `development`. -12. Keep an eye out for any feedback or comments from Split's SDK team. +6. Run the code formatter (`mix format`) and verify that all files are properly formatted. +7. Run the build script (`mix compile`) and the static type analysis (`mix dialyzer`) and make sure it runs with no errors. +8. Run tests (`mix test`) and make sure there are no failures. +9. `git push` your changes to GitHub within your topic branch. +10. Open a Pull Request(PR) from your forked repo and into the `development` branch of the original repository. +11. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project. +12. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `development`. +13. Keep an eye out for any feedback or comments from Split's SDK team. # Contact diff --git a/README.md b/README.md index 1009be6..9d2d47a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This SDK is designed to work with Split, the platform for controlled rollouts, w ## Compatibility -The Elixir Thin Client SDK is compatible with Elixir @TODO and later. +The Elixir Thin Client SDK is compatible with Elixir v1.14.0 and later, and requires [Splitd daemon](https://help.split.io/hc/en-us/articles/18305269686157-Split-Daemon-splitd#local-deployment-recommended) v1.2.0 or later. ## Getting started @@ -30,7 +30,9 @@ After adding the dependency, run `mix deps.get` to fetch the new dependency. ### Using the SDK -Below is a simple example that describes the instantiation and most basic usage of our SDK. Keep in mind that Elixir SDK requires an [SplitD](https://help.split.io/hc/en-us/articles/18305269686157-Split-Daemon-splitd#local-deployment-recommended) instance running in your infrastructure to connect to. +Below is a simple example that describes the instantiation and most basic usage of our SDK. + +**NOTE:** Keep in mind that Elixir SDK requires an [Splitd daemon](https://help.split.io/hc/en-us/articles/18305269686157-Split-Daemon-splitd#local-deployment-recommended) instance running in your infrastructure to connect to, with the link type set to `unix-stream`. ```elixir # Start the SDK supervisor @@ -43,7 +45,7 @@ case Split.get_treatment(user_id, feature_flag_name) do "off" -> # Feature flag is disabled for this user _ -> - # "control" treatment. For example, when feature flag is not found or Elixir SDK wasn't able to connect to SplitD. + # "control" treatment. For example, when feature flag is not found or Elixir SDK wasn't able to connect to Splitd end ``` diff --git a/lib/split.ex b/lib/split.ex index ad29534..6380993 100644 --- a/lib/split.ex +++ b/lib/split.ex @@ -10,27 +10,23 @@ defmodule Split do The most basic approach is to add `Split` as a child of your application's top-most supervisor, i.e. `lib/my_app/application.ex`. - ```elixir - defmodule MyApp.Application do - use Application - - def start(_type, _args) do - children = [ - # ... other children ... - {Split, [socket_path: "/var/run/split.sock"]} - ] - - opts = [strategy: :one_for_one, name: MyApp.Supervisor] - Supervisor.start_link(children, opts) - end - end - ``` + defmodule MyApp.Application do + use Application + + def start(_type, _args) do + children = [ + # ... other children ... + {Split, [socket_path: "/var/run/split.sock"]} + ] + + opts = [strategy: :one_for_one, name: MyApp.Supervisor] + Supervisor.start_link(children, opts) + end + end You can also start `Split` dynamically by calling `Split.Supervisor.start_link/1`: - ```elixir - Split.Supervisor.start_link(opts) - ``` + Split.Supervisor.start_link(opts) ### Options @@ -45,9 +41,7 @@ defmodule Split do Once you have started Split, you are ready to start interacting with the Split.io splitd's daemon to access feature flags and configurations. - ```elixir - Split.get_treatment("user_key", "feature_name") - ``` + Split.get_treatment("user_key", "feature_name") """ alias Split.Telemetry alias Split.Sockets.Pool @@ -56,15 +50,33 @@ defmodule Split do alias Split.RPC.Message alias Split.RPC.ResponseParser - @typedoc "An option that can be provided when starting `Split`." + @typedoc "An option that can be provided when starting `Split`. See [options](#module-options) for more information." @type option :: {:socket_path, String.t()} | {:pool_size, non_neg_integer()} | {:connect_timeout, non_neg_integer()} + @typedoc "Options to start the `Split` application." @type options :: [option()] - @type split_key :: String.t() | {:matching_key, String.t(), :bucketing_key, String.t() | nil} + @typedoc """ + The [traffic type identifier](https://help.split.io/hc/en-us/articles/360019916311-Traffic-types). + It can be either a string or a map with a matching key and an optional bucketing key. + """ + @type split_key :: + String.t() + | %{required(:matchingKey) => String.t(), optional(:bucketingKey) => String.t() | nil} + + @typedoc "A map of attributes to use when evaluating feature flags." + @type attributes :: %{ + optional(atom() | String.t()) => + String.t() | integer() | boolean() | [String.t() | integer()] | nil + } + + @typedoc "A map of properties to use when tracking an event." + @type properties :: %{ + optional(atom() | String.t()) => String.t() | integer() | boolean() | nil + } @doc """ Builds a child specification to use in a Supervisor. @@ -76,7 +88,17 @@ defmodule Split do @spec child_spec(options()) :: Supervisor.child_spec() defdelegate child_spec(options), to: Split.Supervisor - @spec get_treatment(split_key(), String.t(), map() | nil) :: String.t() + @doc """ + Gets the treatment string for a given key, feature flag name and optional attributes. + + ## Examples + + iex> Split.get_treatment("user_id", "located_in_usa") + "off" + iex> Split.get_treatment("user_id", "located_in_usa", %{country: "USA"}) + "on" + """ + @spec get_treatment(split_key(), String.t(), attributes() | nil) :: String.t() def get_treatment(key, feature_name, attributes \\ %{}) do request = Message.get_treatment( @@ -88,7 +110,18 @@ defmodule Split do execute_rpc(request) |> impression_to_treatment() end - @spec get_treatment_with_config(split_key(), String.t(), map() | nil) :: TreatmentWithConfig.t() + @doc """ + Gets the treatment with config for a given key, feature flag name and optional attributes. + + ## Examples + + iex> Split.get_treatment_with_config("user_id", "located_in_usa") + %Split.TreatmentWithConfig{treatment: "off", config: nil} + iex> Split.get_treatment("user_id", "located_in_usa", %{country: "USA"}) + %Split.TreatmentWithConfig{treatment: "on", config: nil} + """ + @spec get_treatment_with_config(split_key(), String.t(), attributes() | nil) :: + TreatmentWithConfig.t() def get_treatment_with_config(key, feature_name, attributes \\ %{}) do request = Message.get_treatment_with_config( @@ -100,7 +133,17 @@ defmodule Split do execute_rpc(request) |> impression_to_treatment_with_config() end - @spec get_treatments(split_key(), [String.t()], map() | nil) :: %{ + @doc """ + Gets a map of feature flag names to treatments for a given key, list of feature flag names and optional attributes. + + ## Examples + + iex> Split.get_treatments("user_id", ["located_in_usa"]) + %{"located_in_usa" => "off"} + iex> Split.get_treatments("user_id", ["located_in_usa"], %{country: "USA"}) + %{"located_in_usa" => "on"} + """ + @spec get_treatments(split_key(), [String.t()], attributes() | nil) :: %{ String.t() => String.t() } def get_treatments(key, feature_names, attributes \\ %{}) do @@ -114,7 +157,17 @@ defmodule Split do execute_rpc(request) |> impressions_to_treatments() end - @spec get_treatments_with_config(split_key(), [String.t()], map() | nil) :: %{ + @doc """ + Gets a map of feature flag names to treatments with config for a given key, list of feature flag names and optional attributes. + + ## Examples + + iex> Split.get_treatments_with_config("user_id", ["located_in_usa"]) + %{"located_in_usa" => %Split.TreatmentWithConfig{treatment: "off", config: nil}} + iex> Split.get_treatments_with_config("user_id", ["located_in_usa"], %{country: "USA"}) + %{"located_in_usa" => %Split.TreatmentWithConfig{treatment: "on", config: nil}} + """ + @spec get_treatments_with_config(split_key(), [String.t()], attributes() | nil) :: %{ String.t() => TreatmentWithConfig.t() } def get_treatments_with_config(key, feature_names, attributes \\ %{}) do @@ -128,7 +181,17 @@ defmodule Split do execute_rpc(request) |> impressions_to_treatments_with_config() end - @spec get_treatments_by_flag_set(split_key(), String.t(), map() | nil) :: %{ + @doc """ + Gets a map of feature flag names to treatment strings for a given key, flag set name and optional attributes. + + ## Examples + + iex> Split.get_treatments_by_flag_set("user_id", "frontend_flags") + %{"located_in_usa" => "off"} + iex> Split.get_treatments_by_flag_set("user_id", "frontend_flags", %{country: "USA"}) + %{"located_in_usa" => "on"} + """ + @spec get_treatments_by_flag_set(split_key(), String.t(), attributes() | nil) :: %{ String.t() => String.t() } def get_treatments_by_flag_set(key, flag_set_name, attributes \\ %{}) do @@ -142,10 +205,20 @@ defmodule Split do execute_rpc(request) |> impressions_to_treatments() end + @doc """ + Gets a map of feature flag names to treatments with config for a given key, flag set name and optional attributes. + + ## Examples + + iex> Split.get_treatments_with_config_by_flag_set("user_id", "frontend_flags") + %{"located_in_usa" => %Split.TreatmentWithConfig{treatment: "off", config: nil}} + iex> Split.get_treatments_with_config_by_flag_set("user_id", "frontend_flags", %{country: "USA"}) + %{"located_in_usa" => %Split.TreatmentWithConfig{treatment: "on", config: nil}} + """ @spec get_treatments_with_config_by_flag_set( split_key(), String.t(), - map() | nil + attributes() | nil ) :: %{String.t() => TreatmentWithConfig.t()} def get_treatments_with_config_by_flag_set( @@ -163,7 +236,17 @@ defmodule Split do execute_rpc(request) |> impressions_to_treatments_with_config() end - @spec get_treatments_by_flag_sets(split_key(), [String.t()], map() | nil) :: + @doc """ + Gets a map of feature flag names to treatment strings for a given key, flag set name and optional attributes. + + ## Examples + + iex> Split.get_treatments_by_flag_sets("user_id", ["frontend_flags", "backend_flags"]) + %{"located_in_usa" => "off"} + iex> Split.get_treatments_by_flag_sets("user_id", ["frontend_flags", "backend_flags"], %{country: "USA"}) + %{"located_in_usa" => "on"} + """ + @spec get_treatments_by_flag_sets(split_key(), [String.t()], attributes() | nil) :: %{String.t() => String.t()} def get_treatments_by_flag_sets( key, @@ -180,10 +263,20 @@ defmodule Split do execute_rpc(request) |> impressions_to_treatments() end + @doc """ + Gets a map of feature flag names to treatments with config for a given key, flag set name and optional attributes. + + ## Examples + + iex> Split.get_treatments_with_config_by_flag_sets("user_id", ["frontend_flags", "backend_flags"]) + %{"located_in_usa" => %Split.TreatmentWithConfig{treatment: "off", config: nil}} + iex> Split.get_treatments_with_config_by_flag_sets("user_id", ["frontend_flags", "backend_flags"], %{country: "USA"}) + %{"located_in_usa" => %Split.TreatmentWithConfig{treatment: "on", config: nil}} + """ @spec get_treatments_with_config_by_flag_sets( split_key(), [String.t()], - map() | nil + attributes() | nil ) :: %{String.t() => TreatmentWithConfig.t()} def get_treatments_with_config_by_flag_sets( @@ -201,18 +294,60 @@ defmodule Split do execute_rpc(request) |> impressions_to_treatments_with_config() end - @spec track(split_key(), String.t(), String.t(), number() | nil, map() | nil) :: boolean() + @doc """ + Tracks an event for a given key, traffic type, event type, and optional numeric value and map of properties. + Returns `true` if the event was successfully tracked, or `false` otherwise, e.g. if the Split daemon is not running or cannot be reached. + + See: https://help.split.io/hc/en-us/articles/26988707417869-Elixir-Thin-Client-SDK#track + + ## Examples + + iex> Split.track("user_id", "user", "my-event") + true + iex> Split.track("user_id", "user", "my-event", 42) + true + iex> Split.track("user_id", "user", "my-event", 42, %{property1: "value1"}) + true + """ + @spec track(split_key(), String.t(), String.t(), number() | nil, properties() | nil) :: + boolean() def track(key, traffic_type, event_type, value \\ nil, properties \\ %{}) do request = Message.track(key, traffic_type, event_type, value, properties) execute_rpc(request) end + @doc """ + Gets the list of all feature flag names. + + ## Examples + + iex> Split.split_names() + ["located_in_usa"] + """ @spec split_names() :: [String.t()] def split_names do request = Message.split_names() execute_rpc(request) end + @doc """ + Gets the data of a given feature flag name in `SplitView` format. + + ## Examples + + iex> Split.split("located_in_usa") + %Split.SplitView{ + name: "located_in_usa", + traffic_type: "user", + killed: false, + treatments: ["on", "off"], + change_number: 123456, + configs: %{ "on" => nil, "off" => nil }, + default_treatment: "off", + sets: ["frontend_flags"], + impressions_disabled: false + } + """ @spec split(String.t()) :: SplitView.t() | nil def split(name) do request = Message.split(name) @@ -220,6 +355,24 @@ defmodule Split do execute_rpc(request) end + @doc """ + Gets the data of all feature flags in `SplitView` format. + + ## Examples + + iex> Split.splits() + [%Split.SplitView{ + name: "located_in_usa", + traffic_type: "user", + killed: false, + treatments: ["on", "off"], + change_number: 123456, + configs: %{ "on" => nil, "off" => nil }, + default_treatment: "off", + sets: ["frontend_flags"], + impressions_disabled: false + }] + """ @spec splits() :: [SplitView.t()] def splits do request = Message.splits() diff --git a/lib/split/rpc/encoder.ex b/lib/split/rpc/encoder.ex index 0c98fc8..a21b0aa 100644 --- a/lib/split/rpc/encoder.ex +++ b/lib/split/rpc/encoder.ex @@ -12,8 +12,19 @@ defmodule Split.RPC.Encoder do iex> message = Message.split("test_split") ...> [_size, encoded] = Encoder.encode(message) ...> Msgpax.unpack!(encoded) - %{"a" => ["test_split"], "o" => 161, "v" => 1} + + + iex> message = Message.get_treatment(key: %{matching_key: "user_id"}, feature_name: "test_split", attributes: %{ :foo => "bar", "baz" => 1 }) + ...> [_size, encoded] = Encoder.encode(message) + ...> Msgpax.unpack!(encoded) + %{"a" => ["user_id", nil, "test_split", %{"baz" => 1, "foo" => "bar"}], "o" => 17, "v" => 1} + + + iex> message = Message.track(%{matching_key: "user_id", bucketing_key: "bucket"}, "user", "purchase", 100.5, %{ "baz" => 1, foo: "bar" }) + ...> [_size, encoded] = Encoder.encode(message) + ...> Msgpax.unpack!(encoded) + %{"a" => ["user_id", "user", "purchase", 100.5, %{"baz" => 1, "foo" => "bar"}], "o" => 128, "v" => 1} """ @spec encode(Message.t()) :: iodata() def encode(message) do diff --git a/lib/split/rpc/message.ex b/lib/split/rpc/message.ex index 62646a5..c0584d7 100644 --- a/lib/split/rpc/message.ex +++ b/lib/split/rpc/message.ex @@ -1,5 +1,5 @@ defmodule Split.RPC.Message do - @doc """ + @moduledoc """ Represents an RPC message to be sent to splitd. """ use Split.RPC.Opcodes @@ -24,12 +24,12 @@ defmodule Split.RPC.Message do @type get_treatment_args :: {:key, Split.split_key()} | {:feature_name, String.t()} - | {:attributes, map() | nil} + | {:attributes, Split.attributes() | nil} @type get_treatments_args :: {:key, Split.split_key()} | {:feature_names, list(String.t())} - | {:attributes, map() | nil} + | {:attributes, Split.attributes() | nil} @doc """ Builds a message to register a client in splitd. @@ -49,7 +49,8 @@ defmodule Split.RPC.Message do iex> Message.get_treatment( ...> key: %{:matching_key => "user_key", :bucketing_key => "bucketing_key"}, - ...> feature_name: "feature_name" + ...> feature_name: "feature_name", + ...> attributes: %{} ...> ) %Message{a: ["user_key", "bucketing_key", "feature_name", %{}], o: 17, v: 1} @@ -68,9 +69,10 @@ defmodule Split.RPC.Message do iex> Message.get_treatment_with_config( ...> key: %{:matching_key => "user_key", :bucketing_key => "bucketing_key"}, - ...> feature_name: "feature_name" + ...> feature_name: "feature_name", + ...> attributes: %{"foo" => "bar", :baz => 1} ...> ) - %Message{a: ["user_key", "bucketing_key", "feature_name", %{}], o: 19, v: 1} + %Message{a: ["user_key", "bucketing_key", "feature_name", %{"foo" => "bar", :baz => 1}], o: 19, v: 1} iex> Message.get_treatment_with_config( ...> key: "user_key", @@ -237,7 +239,7 @@ defmodule Split.RPC.Message do } iex> Message.get_treatments_with_config_by_flag_sets( - ...> key: "user_key", + ...> key: %{:matching_key => "user_key"}, ...> feature_names: ["flag_set_name1", "flag_set_name2"] ...> ) %Message{ @@ -289,7 +291,7 @@ defmodule Split.RPC.Message do ## Examples - iex> Message.track("user_key", "traffic_type", "my_event", 1.5, %{foo: "bar"}) + iex> Message.track("user_key", "traffic_type", "my_event", 1.5, %{:foo => "bar"}) %Message{ v: 1, o: 128, @@ -299,11 +301,19 @@ defmodule Split.RPC.Message do iex> Message.track("user_key", "traffic_type", "my_event") %Message{v: 1, o: 128, a: ["user_key", "traffic_type", "my_event", nil, %{}]} """ - @spec track(String.t(), String.t(), String.t(), any(), map()) :: t() + @spec track(Split.split_key(), String.t(), String.t(), number() | nil, Split.properties()) :: + t() def track(key, traffic_type, event_type, value \\ nil, properties \\ %{}) do + matching_key = + if is_map(key) do + key.matching_key + else + key + end + %__MODULE__{ o: @track_opcode, - a: [key, traffic_type, event_type, value, properties] + a: [matching_key, traffic_type, event_type, value, properties] } end @@ -324,6 +334,18 @@ defmodule Split.RPC.Message do iex> Message.opcode_to_rpc_name(@get_treatments_with_config_opcode) :get_treatments_with_config + iex> Message.opcode_to_rpc_name(@get_treatments_by_flag_set_opcode) + :get_treatments_by_flag_set + + iex> Message.opcode_to_rpc_name(@get_treatments_with_config_by_flag_set_opcode) + :get_treatments_with_config_by_flag_set + + iex> Message.opcode_to_rpc_name(@get_treatments_by_flag_sets_opcode) + :get_treatments_by_flag_sets + + iex> Message.opcode_to_rpc_name(@get_treatments_with_config_by_flag_sets_opcode) + :get_treatments_with_config_by_flag_sets + iex> Message.opcode_to_rpc_name(@split_opcode) :split @@ -364,7 +386,7 @@ defmodule Split.RPC.Message do {matching_key, bucketing_key} = if is_map(key) do - {key.matching_key, key.bucketing_key} + {key.matching_key, Map.get(key, :bucketing_key, nil)} else {key, nil} end diff --git a/lib/split/split_view.ex b/lib/split/split_view.ex index 312e7e7..2a904bf 100644 --- a/lib/split/split_view.ex +++ b/lib/split/split_view.ex @@ -1,4 +1,19 @@ defmodule Split.SplitView do + @moduledoc """ + This module defines a struct that contains information about a feature flag. + + ## Fields + * `:name` - The name of the feature flag + * `:traffic_type` - The traffic type of the feature flag + * `:killed` - A boolean that indicates if the feature flag is killed + * `:treatments` - The list of treatments of the feature flag + * `:change_number` - The change number of the feature flag + * `:configs` - The map of treatments and their configurations + * `:default_treatment` - The default treatment of the feature flag + * `:sets` - The list of flag sets that the feature flag belongs to + * `:impressions_disabled` - A boolean that indicates if the tracking of impressions is disabled + """ + defstruct [ :name, :traffic_type, @@ -17,7 +32,7 @@ defmodule Split.SplitView do killed: boolean(), treatments: [String.t()], change_number: integer(), - configs: map(), + configs: %{String.t() => String.t() | nil}, default_treatment: String.t(), sets: [String.t()], impressions_disabled: boolean() diff --git a/lib/split/supervisor.ex b/lib/split/supervisor.ex index 9dc20b4..999e182 100644 --- a/lib/split/supervisor.ex +++ b/lib/split/supervisor.ex @@ -1,4 +1,8 @@ defmodule Split.Supervisor do + @moduledoc """ + The supervisor for the Split SDK. + """ + use GenServer alias Split.Sockets.Pool @@ -7,7 +11,7 @@ defmodule Split.Supervisor do {:ok, init_arg} end - @spec start_link(keyword()) :: Supervisor.on_start() + @spec start_link(Split.options()) :: Supervisor.on_start() def start_link(opts) do child = {Pool, opts} Supervisor.start_link([child], strategy: :one_for_one) diff --git a/lib/split/treatment_with_config.ex b/lib/split/treatment_with_config.ex index cac3bdd..fe4ee14 100644 --- a/lib/split/treatment_with_config.ex +++ b/lib/split/treatment_with_config.ex @@ -1,4 +1,12 @@ defmodule Split.TreatmentWithConfig do + @moduledoc """ + This module is a struct that represents a treatment with a configuration. + + ## Fields + * `:treatment` - The treatment string value + * `:config` - The treatment configuration string or nil if the treatment has no configuration + """ + defstruct treatment: "control", config: nil diff --git a/mix.exs b/mix.exs index 9166947..2caeccf 100644 --- a/mix.exs +++ b/mix.exs @@ -4,17 +4,27 @@ defmodule SplitThinElixir.MixProject do def project do [ app: :split, - version: "0.2.0-rc.0", + version: "0.2.0", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), runtime_tools: [:observer], package: package(), + docs: [ + filter_modules: fn mod, _meta -> + # Skip modules that are not part of the public API + mod in [ + Split, + Split.Supervisor, + Split.SplitView, + Split.TreatmentWithConfig + ] + end + ] ] end - # Package-specific metadata for Hex.pm defp package do [ @@ -25,7 +35,7 @@ defmodule SplitThinElixir.MixProject do "GitHub" => "https://github.com/splitio/elixir-thin-client", "Docs" => "https://hexdocs.pm/split_thin_sdk" }, - maintainers: ["Emiliano Sanchez", "Nicolas Zelaya", "split-fme-libraries@harness.io"], + maintainers: ["Emiliano Sanchez", "Nicolas Zelaya", "split-fme-libraries@harness.io"] ] end diff --git a/test/rpc/response_parser_test.exs b/test/rpc/response_parser_test.exs index ea1e5bb..6f3c31d 100644 --- a/test/rpc/response_parser_test.exs +++ b/test/rpc/response_parser_test.exs @@ -88,9 +88,9 @@ defmodule Split.RPC.ResponseParserTest do assert ResponseParser.parse_response(response, message) == %{ "feature_name1" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name1", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name1", treatment: "on", label: "test label 1", config: nil, @@ -98,9 +98,9 @@ defmodule Split.RPC.ResponseParserTest do timestamp: 1_723_742_604 }, "feature_name2" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name2", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name2", treatment: "off", label: "test label 2", config: nil, @@ -139,9 +139,9 @@ defmodule Split.RPC.ResponseParserTest do assert ResponseParser.parse_response(response, message) == %{ "feature_name1" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name1", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name1", treatment: "on", label: "test label 1", config: "{\"foo\": \"bar\"}", @@ -149,9 +149,9 @@ defmodule Split.RPC.ResponseParserTest do timestamp: 1_723_742_604 }, "feature_name2" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name2", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name2", treatment: "off", label: "test label 2", config: "{\"baz\": \"qux\"}", @@ -188,9 +188,9 @@ defmodule Split.RPC.ResponseParserTest do assert ResponseParser.parse_response(response, message) == %{ "feature_name1" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name1", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name1", treatment: "on", label: "test label 1", config: nil, @@ -198,9 +198,9 @@ defmodule Split.RPC.ResponseParserTest do timestamp: 1_723_742_604 }, "feature_name2" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name2", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name2", treatment: "off", label: "test label 2", config: nil, @@ -239,9 +239,9 @@ defmodule Split.RPC.ResponseParserTest do assert ResponseParser.parse_response(response, message) == %{ "feature_name1" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name1", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name1", treatment: "on", label: "test label 1", config: "{\"foo\": \"bar\"}", @@ -249,9 +249,9 @@ defmodule Split.RPC.ResponseParserTest do timestamp: 1_723_742_604 }, "feature_name2" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name2", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name2", treatment: "off", label: "test label 2", config: "{\"baz\": \"qux\"}", @@ -288,9 +288,9 @@ defmodule Split.RPC.ResponseParserTest do assert ResponseParser.parse_response(response, message) == %{ "feature_name1" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name1", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name1", treatment: "on", label: "test label 1", config: nil, @@ -298,9 +298,9 @@ defmodule Split.RPC.ResponseParserTest do timestamp: 1_723_742_604 }, "feature_name2" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name2", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name2", treatment: "off", label: "test label 2", config: nil, @@ -339,9 +339,9 @@ defmodule Split.RPC.ResponseParserTest do assert ResponseParser.parse_response(response, message) == %{ "feature_name1" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name1", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name1", treatment: "on", label: "test label 1", config: "{\"foo\": \"bar\"}", @@ -349,9 +349,9 @@ defmodule Split.RPC.ResponseParserTest do timestamp: 1_723_742_604 }, "feature_name2" => %Impression{ - key: "user_key", - bucketing_key: "bucketing_key", - feature: "feature_name2", + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name2", treatment: "off", label: "test label 2", config: "{\"baz\": \"qux\"}", diff --git a/test/sockets/pool_test.exs b/test/sockets/pool_test.exs index f5182b3..66821bd 100644 --- a/test/sockets/pool_test.exs +++ b/test/sockets/pool_test.exs @@ -8,7 +8,7 @@ defmodule Split.Sockets.PoolTest do import ExUnit.CaptureLog setup_all context do - test_id = :erlang.phash2(context.case) + test_id = :erlang.phash2(context.module) socket_path = "/tmp/test-splitd-#{test_id}.sock" start_supervised!( diff --git a/test/split/impression_test.exs b/test/split/impression_test.exs index 06ce010..6ddb3bd 100644 --- a/test/split/impression_test.exs +++ b/test/split/impression_test.exs @@ -16,9 +16,9 @@ defmodule Split.ImpressionTest do } expected = %Impression{ - key: 'user_key', - bucketing_key: 'bucketing_key', - feature: 'feature_name', + key: "user_key", + bucketing_key: "bucketing_key", + feature: "feature_name", treatment: "treatment", label: "label", config: "{\"field\": \"value\"}", @@ -26,7 +26,13 @@ defmodule Split.ImpressionTest do timestamp: 2 } - assert expected == Impression.build_from_daemon_response(treatment_payload, 'user_key', 'bucketing_key', 'feature_name') + assert expected == + Impression.build_from_daemon_response( + treatment_payload, + "user_key", + "bucketing_key", + "feature_name" + ) end test "builds an impression struct with nil values" do @@ -35,9 +41,9 @@ defmodule Split.ImpressionTest do } expected = %Impression{ - key: 'user_key', + key: "user_key", bucketing_key: nil, - feature: 'feature_name', + feature: "feature_name", treatment: "treatment", label: nil, config: nil, @@ -45,7 +51,13 @@ defmodule Split.ImpressionTest do timestamp: nil } - assert expected == Impression.build_from_daemon_response(treatment_payload, 'user_key', nil, 'feature_name') + assert expected == + Impression.build_from_daemon_response( + treatment_payload, + "user_key", + nil, + "feature_name" + ) end end end diff --git a/test/split_test.exs b/test/split_test.exs index a402969..4c45d67 100644 --- a/test/split_test.exs +++ b/test/split_test.exs @@ -6,7 +6,7 @@ defmodule SplitThinElixirTest do alias Split.SplitView setup_all context do - test_id = :erlang.phash2(context.case) + test_id = :erlang.phash2(context.module) socket_path = "/tmp/test-splitd-#{test_id}.sock" start_supervised!(