-
Notifications
You must be signed in to change notification settings - Fork 5
Demonstrate generated Elixir code #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mykulyak
wants to merge
1
commit into
main
Choose a base branch
from
elixir-codegen-demo
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
138 changes: 138 additions & 0 deletions
138
examples/codegen/elixir_api/lib/elixir_api_web/controllers/blocked_times_controller.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| defmodule ElixirApiWeb.BlockedTimesController do | ||
| @moduledoc false | ||
|
|
||
| use ElixirApiWeb, :controller | ||
|
|
||
| action_fallback(ElixirApiWeb.FallbackController) | ||
|
|
||
| # add aliases here | ||
|
|
||
| def index(conn, params) do | ||
| with {:ok, date_from, date_to, location_id, employee_ids} <- parse_index_params(params) do | ||
| # TODO this is the part you need to implement by yourself | ||
| # TODO evaluate extra arguments, then pass them to render() | ||
| render(conn) | ||
| else | ||
| {:error, :invalid_parameters, params} -> | ||
| {:error, :invalid_parameters, params} | ||
|
|
||
| {:error, :invalid_pointers, pointers} -> | ||
| {:error, :invalid_parameters, pointers} | ||
|
|
||
| {:error, :not_found} -> | ||
| {:error, :not_found} | ||
| end | ||
| end | ||
|
|
||
| defp parse_index_params(params) do | ||
| flat_parse( | ||
| params, | ||
| date_from: [:date, :required], | ||
| date_to: :date, | ||
| location_id: [:string, :required], | ||
| employee_ids: :id_list, | ||
| ) | ||
| end | ||
|
|
||
| def create(conn, _params) do | ||
| with {:ok, parsed_opts} <- parse_create_conn(conn) do | ||
| # TODO this is the part you need to implement by yourself | ||
| # TODO evaluate extra arguments, then pass them to render() | ||
| render(conn) | ||
| else | ||
| {:error, :invalid_parameters, params} -> | ||
| {:error, :invalid_parameters, params} | ||
|
|
||
| {:error, :invalid_pointers, pointers} -> | ||
| {:error, :invalid_parameters, pointers} | ||
| end | ||
| end | ||
|
|
||
| defp parse_create_conn(conn) do | ||
| parse( | ||
| conn.assigns[:doc], | ||
| id: [:id, :required], | ||
| attributes: %{ | ||
| start: [&naive_date_time/1, :required], | ||
| end: [&naive_date_time/1, :required], | ||
| note: :string, | ||
| is_private: :boolean, | ||
| }, | ||
| relationships: %{ | ||
| employee: [:resource_id, :required], | ||
| location: [:resource_id, :required], | ||
| }, | ||
| ) | ||
| end | ||
|
|
||
| def update(conn, params) do | ||
| with {:ok, id} <- parse_update_params(params), | ||
| {:ok, parsed_opts} <- parse_update_conn(conn) do | ||
| # TODO this is the part you need to implement by yourself | ||
| # TODO evaluate extra arguments, then pass them to render() | ||
| render(conn) | ||
| else | ||
| {:error, :invalid_parameters, params} -> | ||
| {:error, :invalid_parameters, params} | ||
|
|
||
| {:error, :invalid_pointers, pointers} -> | ||
| {:error, :invalid_parameters, pointers} | ||
|
|
||
| {:error, :not_found} -> | ||
| {:error, :not_found} | ||
| end | ||
| end | ||
|
|
||
| defp parse_update_params(params) do | ||
| flat_parse( | ||
| params, | ||
| id: [:id, :required], | ||
| ) | ||
| end | ||
|
|
||
| defp parse_update_conn(conn) do | ||
| parse( | ||
| conn.assigns[:doc], | ||
| id: [:id, :required], | ||
| attributes: %{ | ||
| start: [&naive_date_time/1, :required], | ||
| end: [&naive_date_time/1, :required], | ||
| note: :string, | ||
| is_private: :boolean, | ||
| }, | ||
| relationships: %{ | ||
| employee: :resource_id, | ||
| }, | ||
| ) | ||
| end | ||
|
|
||
| def delete(conn, params) do | ||
| with {:ok, id} <- parse_delete_params(params) do | ||
| # TODO this is the part you need to implement by yourself | ||
| # TODO evaluate extra arguments, then pass them to render() | ||
| render(conn) | ||
| else | ||
| {:error, :invalid_parameters, params} -> | ||
| {:error, :invalid_parameters, params} | ||
|
|
||
| {:error, :invalid_pointers, pointers} -> | ||
| {:error, :invalid_parameters, pointers} | ||
| end | ||
| end | ||
|
|
||
| defp parse_delete_params(params) do | ||
| flat_parse( | ||
| params, | ||
| id: [:id, :required], | ||
| ) | ||
| end | ||
|
|
||
| defp naive_date_time(nil), do: {:error, :invalid_date_time} | ||
|
|
||
| defp naive_date_time(input) do | ||
| case NaiveDateTime.from_iso8601(input) do | ||
| {:ok, date_time} -> {:ok, date_time} | ||
| {:error, _} -> {:error, :invalid_date_time} | ||
| end | ||
| end | ||
| end |
69 changes: 69 additions & 0 deletions
69
...ples/codegen/elixir_api/lib/elixir_api_web/controllers/blocked_times_export_controller.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| defmodule ElixirApiWeb.BlockedTimesExportController do | ||
| @moduledoc false | ||
|
|
||
| use ElixirApiWeb, :controller | ||
|
|
||
| action_fallback(ElixirApiWeb.FallbackController) | ||
|
|
||
| # add aliases here | ||
|
|
||
| def create(conn, _params) do | ||
| with {:ok, parsed_opts} <- parse_create_conn(conn) do | ||
| # TODO this is the part you need to implement by yourself | ||
| # TODO evaluate extra arguments, then pass them to render() | ||
| render(conn) | ||
| else | ||
| {:error, :invalid_parameters, params} -> | ||
| {:error, :invalid_parameters, params} | ||
|
|
||
| {:error, :invalid_pointers, pointers} -> | ||
| {:error, :invalid_parameters, pointers} | ||
| end | ||
| end | ||
|
|
||
| defp parse_create_conn(conn) do | ||
| parse( | ||
| conn.assigns[:doc], | ||
| id: [:id, :required], | ||
| attributes: %{ | ||
| start: [&naive_date_time/1, :required], | ||
| end: [&naive_date_time/1, :required], | ||
| }, | ||
| relationships: %{ | ||
| }, | ||
| ) | ||
| end | ||
|
|
||
| def show(conn, params) do | ||
| with {:ok, id} <- parse_show_params(params) do | ||
| # TODO this is the part you need to implement by yourself | ||
| # TODO evaluate extra arguments, then pass them to render() | ||
| render(conn) | ||
| else | ||
| {:error, :invalid_parameters, params} -> | ||
| {:error, :invalid_parameters, params} | ||
|
|
||
| {:error, :invalid_pointers, pointers} -> | ||
| {:error, :invalid_parameters, pointers} | ||
|
|
||
| {:error, :not_found} -> | ||
| {:error, :not_found} | ||
| end | ||
| end | ||
|
|
||
| defp parse_show_params(params) do | ||
| flat_parse( | ||
| params, | ||
| id: [:id, :required], | ||
| ) | ||
| end | ||
|
|
||
| defp naive_date_time(nil), do: {:error, :invalid_date_time} | ||
|
|
||
| defp naive_date_time(input) do | ||
| case NaiveDateTime.from_iso8601(input) do | ||
| {:ok, date_time} -> {:ok, date_time} | ||
| {:error, _} -> {:error, :invalid_date_time} | ||
| end | ||
| end | ||
| end | ||
90 changes: 90 additions & 0 deletions
90
examples/codegen/elixir_api/lib/elixir_api_web/controllers/fallback_controller.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| defmodule ElixirApiWeb.FallbackController do | ||
| @moduledoc false | ||
|
|
||
| use ElixirApiWeb, :controller | ||
| alias ElixirApiWeb.ErrorView | ||
|
|
||
| def call(conn, {:error, :bad_request}) do | ||
| conn | ||
| |> put_status(:bad_request) | ||
| |> put_view(ErrorView) | ||
| |> render(:"400") | ||
| end | ||
|
|
||
| def call(conn, {:error, :bad_request, code}) when is_atom(code) do | ||
| conn | ||
| |> put_status(:bad_request) | ||
| |> put_view(ErrorView) | ||
| |> render(:"400", code: Atom.to_string(code)) | ||
| end | ||
|
|
||
| def call(conn, {:error, :invalid_parameters, params}) when is_list(params) do | ||
| conn | ||
| |> put_status(:bad_request) | ||
| |> put_view(ErrorView) | ||
| |> render(:"400", params: params) | ||
| end | ||
|
|
||
| def call(conn, {:error, :invalid_pointers, pointers}) when is_list(pointers) do | ||
| conn | ||
| |> put_status(:bad_request) | ||
| |> put_view(ErrorView) | ||
| |> render(:"400", pointers: pointers) | ||
| end | ||
|
|
||
| def call(conn, {:error, :unauthorized}) do | ||
| conn | ||
| |> put_status(:unauthorized) | ||
| |> put_view(ErrorView) | ||
| |> render(:"401") | ||
| end | ||
|
|
||
| def call(conn, {:error, :forbidden}) do | ||
| conn | ||
| |> put_status(:forbidden) | ||
| |> put_view(ErrorView) | ||
| |> render(:"403") | ||
| end | ||
|
|
||
| def call(conn, {:error, :not_found}) do | ||
| conn | ||
| |> put_status(:not_found) | ||
| |> put_view(ErrorView) | ||
| |> render(:"404") | ||
| end | ||
|
|
||
| def call(conn, {:error, %Ecto.Changeset{} = changeset}) do | ||
| conn | ||
| |> put_status(:unprocessable_entity) | ||
| |> put_view(ErrorView) | ||
| |> render(:"422", changeset: changeset) | ||
| end | ||
|
|
||
| def call(conn, {:error, :validation_failed}) do | ||
| conn | ||
| |> put_status(:unprocessable_entity) | ||
| |> put_view(ErrorView) | ||
| |> render(:"422") | ||
| end | ||
|
|
||
| def call(conn, {:error, :unprocessable_entity}) do | ||
| conn | ||
| |> put_status(:unprocessable_entity) | ||
| |> put_view(ErrorView) | ||
| |> render(:"422") | ||
| end | ||
|
|
||
| def call(conn, {:error, :conflict}) do | ||
| conn | ||
| |> put_status(:conflict) | ||
| |> put_view(ErrorView) | ||
| |> render(:"409") | ||
| end | ||
|
|
||
| def call(conn, {:error, :not_available}) do | ||
| conn | ||
| |> put_status(:service_unavailable) | ||
| |> put_view(ErrorView) | ||
| |> render(:"503") | ||
| end | ||
| end |
44 changes: 44 additions & 0 deletions
44
examples/codegen/elixir_api/lib/elixir_api_web/resources/blocked_times_resource.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| defmodule ElixirApiWeb.BlockedTimesResource do | ||
| @moduledoc false | ||
|
|
||
| @resource_type "blocked-times" | ||
| use Jabbax.Document | ||
| alias ElixirApiWeb.EmployeesResource | ||
| alias ElixirApiWeb.LocationsResource | ||
|
|
||
| def build(config) do | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to also generate typespecs for the resources |
||
| %Resource{ | ||
| type: @resource_type, | ||
| id: config.id, | ||
| attributes: %{ | ||
| start: config.start, | ||
| end: config.end, | ||
| note: config.note, | ||
| is_private: config.is_private, | ||
| }, | ||
| relationships: | ||
| %{} | ||
| |> link_relationship(:employee, config.employee) | ||
| |> link_relationship(:location, config.location) | ||
| } | ||
| end | ||
|
|
||
| def link(config) do | ||
| %ResourceId{ | ||
| type: @resource_type, | ||
| id: config.id, | ||
| } | ||
| end | ||
|
|
||
| defp link_relationship(relationships, type, nil) do | ||
| Map.put(relationships, type, %Jabbax.Document.Relationship{data: nil}) | ||
| end | ||
|
|
||
| defp link_relationship(relationships, :employee, employee) do | ||
| Map.put(relationships, :employee, EmployeesResource.link(employee)) | ||
| end | ||
|
|
||
| defp link_relationship(relationships, :location, location) do | ||
| Map.put(relationships, :location, LocationsResource.link(location)) | ||
| end | ||
| end | ||
13 changes: 13 additions & 0 deletions
13
examples/codegen/elixir_api/lib/elixir_api_web/resources/employees_resource.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| defmodule ElixirApiWeb.EmployeesResource do | ||
| @moduledoc false | ||
|
|
||
| @resource_type "employees" | ||
| use Jabbax.Document | ||
|
|
||
| def link(config) do | ||
| %ResourceId{ | ||
| type: @resource_type, | ||
| id: config.id, | ||
| } | ||
| end | ||
| end |
24 changes: 24 additions & 0 deletions
24
examples/codegen/elixir_api/lib/elixir_api_web/resources/export_jobs_resource.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| defmodule ElixirApiWeb.ExportJobsResource do | ||
| @moduledoc false | ||
|
|
||
| @resource_type "export-jobs" | ||
| use Jabbax.Document | ||
|
|
||
| def build(config) do | ||
| %Resource{ | ||
| type: @resource_type, | ||
| id: config.id, | ||
| attributes: %{ | ||
| status: config.status, | ||
| download_link: config.download_link, | ||
| }, | ||
| } | ||
| end | ||
|
|
||
| def link(config) do | ||
| %ResourceId{ | ||
| type: @resource_type, | ||
| id: config.id, | ||
| } | ||
| end | ||
| end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great start!
One obvious issue I'm seeing is that after the developer implements the missing parts they can't regenerate the thing without undoing their work.
The answer to this is ofc better modularity
We should make the thing modular enough that The controller code is actually dumb enough to not need any manual changes ever. That would require us to agree on some conventions as to what the code in the controller action should look like and what should it call into and what the error handling should look like.
same thing is obiously true about the views, the tests, the router, etc.
There are many ways to achieve this separation between generated and nongenerated code.
It's not ease to think of optimal patterns for this from the top of my head.
Another thing that comes to my mind when trying to think of the patterns is that
if we make the generator smart enough to know that If an action/test/view already exists, then It should not regenerate it then it would be much easier to make the separation natural and not requiring splitting things into many files. Wdyt? I know it's not easy to make a foolproof mechanism like that without actually parsing the code (or using mysterious nonstandard extended regexes), so it might not be worth to introduce this complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to get the most value from the spec->code workflow then we have to enable painfree, nondestructive generation. I'm stating the obvious here - generation of a new endpoint should not destroy a whole controller, etc