Skip to content

Commit e5a3f30

Browse files
Create elixir map to proto converter
1 parent 6493f5d commit e5a3f30

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

ee/ephemeral_environments/lib/ephemeral_environments/grpc/ephemeral_environments_server.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ defmodule EphemeralEnvironments.Grpc.EphemeralEnvironmentsServer do
2525
end
2626

2727
def create(request, _stream) do
28-
ret = EphemeralEnvironments.Service.EphemeralEnvironmentType.create(request.environment_type)
29-
%CreateResponse{}
28+
{:ok, ret} = EphemeralEnvironments.Service.EphemeralEnvironmentType.create(request.environment_type)
29+
converted = EphemeralEnvironments.Utils.Proto.from_map(%{environment_type: ret}, CreateResponse)
30+
converted
3031
end
3132

3233
def update(_request, _stream) do

ee/ephemeral_environments/lib/ephemeral_environments/repo/ephemeral_environment_type.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ defmodule EphemeralEnvironments.Repo.EphemeralEnvironmentType do
1717
timestamps()
1818
end
1919

20-
@doc false
2120
def changeset(ephemeral_environment_type, attrs) do
2221
ephemeral_environment_type
2322
|> cast(attrs, [

ee/ephemeral_environments/lib/ephemeral_environments/service/ephemeral_environment_type.ex

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule EphemeralEnvironments.Service.EphemeralEnvironmentType do
1515
- max_number_of_instances (optional)
1616
1717
## Returns
18-
- {:ok, %Schema{}} on success
18+
- {:ok, map} on success
1919
- {:error, String.t()} on validation failure
2020
"""
2121
def create(attrs) do
@@ -26,11 +26,21 @@ defmodule EphemeralEnvironments.Service.EphemeralEnvironmentType do
2626
|> Schema.changeset(attrs)
2727
|> Repo.insert()
2828
|> case do
29-
{:ok, record} -> {:ok, record}
29+
{:ok, record} -> {:ok, struct_to_map(record)}
3030
{:error, changeset} -> {:error, format_errors(changeset)}
3131
end
3232
end
3333

34+
###
35+
### Helper functions
36+
###
37+
38+
defp struct_to_map(struct) do
39+
struct
40+
|> Map.from_struct()
41+
|> Map.drop([:__meta__])
42+
end
43+
3444
defp format_errors(changeset) do
3545
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
3646
Enum.reduce(opts, msg, fn {key, value}, acc ->

ee/ephemeral_environments/lib/ephemeral_environments/utils/proto.ex

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
11
defmodule EphemeralEnvironments.Utils.Proto do
22
@moduledoc """
3-
Utility functions for converting protobuf structs to plain Elixir maps.
3+
Utility functions for converting between protobuf structs and plain Elixir maps.
44
"""
55

6+
@doc """
7+
Converts an Elixir map to a protobuf struct of the given module type.
8+
- Converts DateTime to Google.Protobuf.Timestamp
9+
- Converts normalized enum atoms (:ready) to protobuf enum atoms (:TYPE_STATE_READY)
10+
- Recursively processes nested maps to nested proto structs
11+
12+
## Examples
13+
14+
from_map(%{name: "test", state: :ready}, InternalApi.EphemeralEnvironments.EphemeralEnvironmentType)
15+
"""
16+
def from_map(nil, _module), do: nil
17+
18+
def from_map(map, module) when is_map(map) and is_atom(module) do
19+
field_props = module.__message_props__().field_props
20+
21+
# Convert map to struct fields
22+
fields =
23+
map
24+
|> Enum.map(fn {key, value} ->
25+
# Find field info for this key
26+
field_info = find_field_info(field_props, key)
27+
converted_value = convert_value_from_map(value, field_info)
28+
{key, converted_value}
29+
end)
30+
|> Enum.into(%{})
31+
32+
struct(module, fields)
33+
end
34+
635
@doc """
736
Recursively converts a protobuf struct to a plain Elixir map.
837
- Converts Google.Protobuf.Timestamp to DateTime
@@ -116,4 +145,80 @@ defmodule EphemeralEnvironments.Utils.Proto do
116145
|> Macro.underscore()
117146
|> String.upcase()
118147
end
148+
149+
# Find field info by field name atom
150+
defp find_field_info(field_props, field_name) do
151+
field_props
152+
|> Enum.find(fn {_num, props} -> props.name_atom == field_name end)
153+
|> case do
154+
{_num, props} -> props
155+
nil -> nil
156+
end
157+
end
158+
159+
defp convert_value_from_map(nil, _field_info), do: nil
160+
161+
defp convert_value_from_map(%DateTime{} = dt, _field_info) do
162+
%Google.Protobuf.Timestamp{
163+
seconds: DateTime.to_unix(dt),
164+
nanos: 0
165+
}
166+
end
167+
168+
@unix_epoch ~N[1970-01-01 00:00:00]
169+
defp convert_value_from_map(%NaiveDateTime{} = ndt, _field_info) do
170+
%Google.Protobuf.Timestamp{
171+
seconds: NaiveDateTime.diff(ndt, @unix_epoch)
172+
}
173+
end
174+
175+
defp convert_value_from_map(value, nil), do: value
176+
177+
defp convert_value_from_map(values, field_info) when is_list(values) do
178+
if field_info.embedded? do
179+
Enum.map(values, fn item ->
180+
if is_map(item) and not is_struct(item) do
181+
from_map(item, field_info.type)
182+
else
183+
item
184+
end
185+
end)
186+
else
187+
values
188+
end
189+
end
190+
191+
# Handle nested maps (embedded messages)
192+
defp convert_value_from_map(value, field_info) when is_map(value) and not is_struct(value) do
193+
if field_info.embedded? do
194+
from_map(value, field_info.type)
195+
else
196+
value
197+
end
198+
end
199+
200+
# Handle enum atoms - convert normalized atom back to proto enum
201+
defp convert_value_from_map(value, field_info) when is_atom(value) do
202+
if field_info.enum? do
203+
case field_info.type do
204+
{:enum, enum_module} -> denormalize_enum_name(value, enum_module)
205+
_ -> value
206+
end
207+
else
208+
value
209+
end
210+
end
211+
212+
defp convert_value_from_map(value, _field_info), do: value
213+
214+
# Denormalize enum: :ready -> :TYPE_STATE_READY
215+
defp denormalize_enum_name(normalized_atom, enum_module) do
216+
prefix = extract_enum_prefix(enum_module)
217+
218+
normalized_atom
219+
|> Atom.to_string()
220+
|> String.upcase()
221+
|> then(&"#{prefix}_#{&1}")
222+
|> String.to_atom()
223+
end
119224
end

0 commit comments

Comments
 (0)