Skip to content

Commit

Permalink
feat: Detour serializer (#2899)
Browse files Browse the repository at this point in the history
Co-authored-by: Kayla Firestack <firestack@users.noreply.github.com>
  • Loading branch information
hannahpurcell and firestack authored Nov 19, 2024
1 parent 7d54821 commit 526e12f
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 5 deletions.
3 changes: 2 additions & 1 deletion lib/skate/detours/detours.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Skate.Detours.Detours do
import Ecto.Query, warn: false
alias Skate.Repo
alias Skate.Detours.Db.Detour
alias Skate.Detours.SnapshotSerde
alias Skate.Detours.Detour.Detailed, as: DetailedDetour
alias Skate.Detours.Detour.WithState, as: DetourWithState
alias Skate.Settings.User
Expand Down Expand Up @@ -155,7 +156,7 @@ defmodule Skate.Detours.Detours do
|> Repo.one!()

%DetourWithState{
state: detour.state,
state: SnapshotSerde.serialize(detour),
updated_at: timestamp_to_unix(detour.updated_at),
author: detour.author.email
}
Expand Down
265 changes: 265 additions & 0 deletions lib/skate/detours/snapshot_serde.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ defmodule Skate.Detours.SnapshotSerde do
Serializes Ecto Database Structs into XState JSON Snapshots
"""

require Logger

alias Skate.Detours.Db.Detour

@doc """
Converts a XState JSON Snapshot to Detours Database Changeset
"""
Expand All @@ -27,4 +31,265 @@ defmodule Skate.Detours.SnapshotSerde do
"""
def id_from_snapshot(%{"context" => %{"uuid" => id}}), do: id
def id_from_snapshot(%{"context" => %{}}), do: nil

@doc """
Builds XState Snapshot from Detours Database object
"""
def serialize(%Detour{} = detour) do
validate_serialized_snapshot(
%{
"value" => state_from_detour(detour),
"status" => "active",
"context" => context_from_detour(detour),
"children" => snapshot_children_from_detour(detour),
"historyValue" => %{}
},
detour
)
end

defp validate_serialized_snapshot(
serialized_snapshot,
%Detour{id: id, state: state}
) do
if serialized_snapshot === state do
serialized_snapshot
else
Logger.error(
"Serialized detour doesn't match saved snapshot. Falling back to snapshot for detour_id=#{id} diff=#{inspect(MapDiff.diff(state, serialized_snapshot))}"
)

state
end
end

# For each of these retrieve functions, the first function is the one
# that will look for the correct field on the db object, once the detour state
# is properly distributed across db tables / columns.
#
# The second function is the fallback that uses the existing state snapshot.
#
# Note that the first function is only stubbed out and will be significantly updated
# when the new fields are added to the db. For example, `get_route_from_db_detour`
# suggests that the field `route` will be surfaced at the top level of the detour
# but actually, it may be nested under `route_pattern` or however else we organize
# it in the db.

defp context_from_detour(%Detour{} = detour) do
:maps.filter(fn _, v -> v != nil end, %{
"uuid" => uuid_from_detour(detour),
"route" => route_from_detour(detour),
"routePattern" => routepattern_from_detour(detour),
"routePatterns" => routepatterns_from_detour(detour),
"startPoint" => startpoint_from_detour(detour),
"endPoint" => endpoint_from_detour(detour),
"waypoints" => waypoints_from_detour(detour),
"nearestIntersection" => nearestintersection_from_detour(detour),
"detourShape" => detourshape_from_detour(detour),
"finishedDetour" => finisheddetour_from_detour(detour),
"editedDirections" => editeddirections_from_detour(detour),
"selectedDuration" => selectedduration_from_detour(detour),
"selectedReason" => selectedreason_from_detour(detour)
})
end

defmacrop log_fallback(field) do
quote do
Logger.warning("Unexpected detour structure. Using snapshot for field: #{unquote(field)}")
end
end

# defp state_from_detour(%Detour{detour_state: state}), do: state
defp state_from_detour(%Detour{
state: %{
"value" => state
}
}) do
log_fallback("state")
state
end

defp state_from_detour(_), do: nil

defp uuid_from_detour(%Detour{id: id}), do: id

# defp route_from_detour(%Detour{route: route}), do: route
defp route_from_detour(%Detour{
state: %{
"context" => %{
"route" => route
}
}
}) do
log_fallback("route")
route
end

defp route_from_detour(_), do: nil

# defp routepattern_from_detour(%Detour{route_pattern: route_pattern}), do: route_pattern
defp routepattern_from_detour(%Detour{
state: %{
"context" => %{
"routePattern" => route_pattern
}
}
}) do
log_fallback("routePattern")
route_pattern
end

defp routepattern_from_detour(_), do: nil

# defp routepatterns_from_detour(%Detour{route_patterns: route_patterns}), do: route_patterns
defp routepatterns_from_detour(%Detour{
state: %{
"context" => %{
"routePatterns" => route_patterns
}
}
}) do
log_fallback("route_patterns")
route_patterns
end

defp routepatterns_from_detour(_), do: nil

# defp startpoint_from_detour(%Detour{start_point: start_point}), do: start_point
defp startpoint_from_detour(%Detour{
state: %{
"context" => %{
"startPoint" => start_point
}
}
}) do
log_fallback("startPoint")
start_point
end

defp startpoint_from_detour(_), do: nil

# defp endpoint_from_detour(%Detour{end_point: end_point}), do: end_point
defp endpoint_from_detour(%Detour{
state: %{
"context" => %{
"endPoint" => end_point
}
}
}) do
log_fallback("endPoint")
end_point
end

defp endpoint_from_detour(_), do: nil

# defp waypoints_from_detour(%Detour{waypoints: waypoints}), do: waypoints
defp waypoints_from_detour(%Detour{
state: %{
"context" => %{
"waypoints" => waypoints
}
}
}) do
log_fallback("waypoints")
waypoints
end

defp waypoints_from_detour(_), do: nil

# defp nearestintersection_from_detour(%Detour{nearest_intersection: nearest_intersection}), do: nearest_intersection
defp nearestintersection_from_detour(%Detour{
state: %{
"context" => %{
"nearestIntersection" => nearest_intersection
}
}
}) do
log_fallback("nearestIntersection")
nearest_intersection
end

defp nearestintersection_from_detour(_), do: nil

# defp detourshape_from_detour(%Detour{detour_shape: detour_shape}), do: detour_shape
defp detourshape_from_detour(%Detour{
state: %{
"context" => %{
"detourShape" => detour_shape
}
}
}) do
log_fallback("detourShape")
detour_shape
end

defp detourshape_from_detour(_), do: nil

# defp finisheddetour_from_detour(%Detour{finished_detour: finished_detour}), do: finished_detour
defp finisheddetour_from_detour(%Detour{
state: %{
"context" => %{
"finishedDetour" => finished_detour
}
}
}) do
log_fallback("finishedDetour")
finished_detour
end

defp finisheddetour_from_detour(_), do: nil

# defp editeddirections_from_detour(%Detour{finished_detour: finished_detour}), do: finished_detour
defp editeddirections_from_detour(%Detour{
state: %{
"context" => %{
"editedDirections" => edited_directions
}
}
}) do
log_fallback("editedDirections")
edited_directions
end

defp editeddirections_from_detour(_), do: nil

# defp selectedduration_from_detour(%Detour{snapshot_children: snapshot_children}), do: snapshot_children
defp selectedduration_from_detour(%Detour{
state: %{
"context" => %{
"selectedDuration" => selected_duration
}
}
}) do
log_fallback("selectedDuration")
selected_duration
end

defp selectedduration_from_detour(_), do: nil

# defp selectedreason_from_detour(%Detour{snapshot_children: snapshot_children}), do: snapshot_children
defp selectedreason_from_detour(%Detour{
state: %{
"context" => %{
"selectedReason" => selected_reason
}
}
}) do
log_fallback("selectedReason")
selected_reason
end

defp selectedreason_from_detour(_), do: nil

# defp snapshot_children_from_detour(%Detour{snapshot_children: snapshot_children}), do: snapshot_children
defp snapshot_children_from_detour(%Detour{
state: %{
"children" => snapshot_children
}
}) do
log_fallback("children")
snapshot_children
end

defp snapshot_children_from_detour(_), do: nil
end
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ defmodule Skate.MixProject do
{:jason, "~> 1.0"},
{:lcov_ex, "~> 0.2", only: [:dev, :test], runtime: false},
{:logster, "~> 1.0"},
{:map_diff, "~> 1.3.4"},
{:mox, "~> 1.1.0", only: :test},
{:oban, "~> 2.15"},
{:phoenix, "~> 1.7.0"},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.4", "29563475afa9b8a2add1b7a9c8fb68d06ca7737648f28398e04461f008b69521", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29"},
"map_diff": {:hex, :map_diff, "1.3.4", "4fa013ad4fff7b21694f3aa5890dad5a0679f11812fdd89a0ad06276a431faf8", [:mix], [], "hexpm", "32fc0b8fc158683a00a58298440b8cb884e7e779f9459e598df61d022b5412e9"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
Expand Down
Loading

0 comments on commit 526e12f

Please sign in to comment.