Skip to content

Commit 1ff1469

Browse files
feat: Extract FF handling to separate class
1 parent 1ea4a4e commit 1ff1469

File tree

2 files changed

+138
-51
lines changed

2 files changed

+138
-51
lines changed

lib/posthog/client.ex

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -273,42 +273,8 @@ defmodule Posthog.Client do
273273
|> Enum.reduce(%{distinct_id: distinct_id}, fn {k, v}, acc -> Map.put(acc, k, v) end)
274274

275275
case post!("/decide?v=4", body, headers(opts[:headers])) do
276-
{:ok, %{body: body}} ->
277-
if Map.has_key?(body, "flags") do
278-
flags = body["flags"]
279-
280-
feature_flags =
281-
Map.new(flags, fn {k, v} ->
282-
{k, if(v["variant"], do: v["variant"], else: v["enabled"])}
283-
end)
284-
285-
feature_flag_payloads =
286-
Map.new(flags, fn {k, v} ->
287-
{k,
288-
if(v["metadata"]["payload"],
289-
do: decode_feature_flag_payload(v["metadata"]["payload"]),
290-
else: nil
291-
)}
292-
end)
293-
294-
{:ok,
295-
%{
296-
flags: flags,
297-
feature_flags: feature_flags,
298-
feature_flag_payloads: feature_flag_payloads,
299-
request_id: body["requestId"]
300-
}}
301-
else
302-
{:ok,
303-
%{
304-
feature_flags: Map.get(body, "featureFlags", %{}),
305-
feature_flag_payloads: decode_feature_flag_payloads(body),
306-
request_id: body["requestId"]
307-
}}
308-
end
309-
310-
err ->
311-
err
276+
{:ok, %{body: body}} -> {:ok, Posthog.FeatureFlag.process_response(body)}
277+
err -> err
312278
end
313279
end
314280

@@ -396,19 +362,4 @@ defmodule Posthog.Client do
396362
"$lib_version" => @lib_version
397363
}
398364
end
399-
400-
defp decode_feature_flag_payloads(data) do
401-
data
402-
|> Map.get("featureFlagPayloads", %{})
403-
|> Enum.reduce(%{}, fn {k, v}, map ->
404-
Map.put(map, k, decode_feature_flag_payload(v))
405-
end)
406-
end
407-
408-
defp decode_feature_flag_payload(payload) do
409-
case Jason.decode(payload) do
410-
{:ok, decoded} -> decoded
411-
{:error, _} -> payload
412-
end
413-
end
414365
end

lib/posthog/feature_flag.ex

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,140 @@ defmodule Posthog.FeatureFlag do
104104
"""
105105
@spec boolean?(t()) :: boolean()
106106
def boolean?(%__MODULE__{enabled: value}), do: is_boolean(value)
107+
108+
@doc """
109+
Processes a feature flag response from the PostHog API.
110+
Handles both v3 and v4 API response formats.
111+
112+
## Parameters
113+
114+
* `response` - The raw response from the API
115+
116+
## Examples
117+
118+
# v4 API response
119+
response = %{
120+
"flags" => %{
121+
"my-flag" => %{
122+
"enabled" => true,
123+
"variant" => nil,
124+
"metadata" => %{"payload" => "{\"color\": \"blue\"}"}
125+
}
126+
},
127+
"requestId" => "123"
128+
}
129+
Posthog.FeatureFlag.process_response(response)
130+
# Returns: %{
131+
# flags: %{"my-flag" => %{"enabled" => true, "variant" => nil, "metadata" => %{"payload" => "{\"color\": \"blue\"}"}}},
132+
# feature_flags: %{"my-flag" => true},
133+
# feature_flag_payloads: %{"my-flag" => %{"color" => "blue"}},
134+
# request_id: "123"
135+
# }
136+
137+
# v3 API response
138+
response = %{
139+
"featureFlags" => %{"my-flag" => true},
140+
"featureFlagPayloads" => %{"my-flag" => "{\"color\": \"blue\"}"},
141+
"requestId" => "123"
142+
}
143+
Posthog.FeatureFlag.process_response(response)
144+
# Returns: %{
145+
# feature_flags: %{"my-flag" => true},
146+
# feature_flag_payloads: %{"my-flag" => %{"color" => "blue"}},
147+
# request_id: "123"
148+
# }
149+
"""
150+
@spec process_response(map()) :: %{
151+
flags: map() | nil,
152+
feature_flags: %{optional(binary()) => variant()},
153+
feature_flag_payloads: %{optional(binary()) => term()},
154+
request_id: binary() | nil
155+
}
156+
def process_response(%{"flags" => flags} = response) do
157+
feature_flags =
158+
Map.new(flags, fn {k, v} ->
159+
{k, if(v["variant"], do: v["variant"], else: v["enabled"])}
160+
end)
161+
162+
feature_flag_payloads =
163+
Map.new(flags, fn {k, v} ->
164+
{k,
165+
if(v["metadata"]["payload"],
166+
do: decode_payload(v["metadata"]["payload"]),
167+
else: nil
168+
)}
169+
end)
170+
171+
%{
172+
flags: flags,
173+
feature_flags: feature_flags,
174+
feature_flag_payloads: feature_flag_payloads,
175+
request_id: response["requestId"]
176+
}
177+
end
178+
179+
def process_response(response) do
180+
%{
181+
flags: nil,
182+
feature_flags: Map.get(response, "featureFlags", %{}),
183+
feature_flag_payloads: decode_payloads(Map.get(response, "featureFlagPayloads", %{})),
184+
request_id: response["requestId"]
185+
}
186+
end
187+
188+
@doc """
189+
Decodes a map of feature flag payloads.
190+
191+
## Parameters
192+
193+
* `payloads` - Map of feature flag names to their payload values
194+
195+
## Examples
196+
197+
payloads = %{
198+
"my-flag" => "{\"color\": \"blue\"}",
199+
"other-flag" => "plain-text"
200+
}
201+
Posthog.FeatureFlag.decode_payloads(payloads)
202+
# Returns: %{
203+
# "my-flag" => %{"color" => "blue"},
204+
# "other-flag" => "plain-text"
205+
# }
206+
"""
207+
@spec decode_payloads(%{optional(binary()) => term()}) :: %{optional(binary()) => term()}
208+
def decode_payloads(payloads) do
209+
Enum.reduce(payloads, %{}, fn {k, v}, map ->
210+
Map.put(map, k, decode_payload(v))
211+
end)
212+
end
213+
214+
@doc """
215+
Decodes a feature flag payload from JSON string to Elixir term.
216+
Returns the original payload if it's not a valid JSON string.
217+
218+
## Examples
219+
220+
# JSON string payload
221+
Posthog.FeatureFlag.decode_payload("{\"color\": \"blue\"}")
222+
# Returns: %{"color" => "blue"}
223+
224+
# Non-JSON string payload
225+
Posthog.FeatureFlag.decode_payload("plain-text")
226+
# Returns: "plain-text"
227+
228+
# Nil payload
229+
Posthog.FeatureFlag.decode_payload(nil)
230+
# Returns: nil
231+
"""
232+
@spec decode_payload(term()) :: term()
233+
def decode_payload(nil), do: nil
234+
235+
def decode_payload(payload) when is_binary(payload) do
236+
case Posthog.Config.json_library().decode(payload) do
237+
{:ok, decoded} -> decoded
238+
{:error, _} -> payload
239+
end
240+
end
241+
242+
def decode_payload(payload), do: payload
107243
end

0 commit comments

Comments
 (0)