-module(rally@generator@ssr_handler).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/generator/ssr_handler.gleam").
-export([generate/12]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" SSR handler codegen.\n"
"\n"
" Generates ssr_handler.gleam: takes a parsed Route, calls the page's\n"
" load function, renders view wrapped in layout, embeds the model as\n"
" base64 flags for client hydration. Handles auth resolve, from_session,\n"
" authorize, and LoadResult (Page/Redirect with cookies) when auth is\n"
" configured.\n"
).
-file("src/rally/generator/ssr_handler.gleam", 295).
-spec marker_helper(boolean()) -> binary().
marker_helper(Has_load_pages) ->
case Has_load_pages of
true ->
Body = [<<"fn insert_rendered(shell: String, rendered: String) -> String {"/utf8>>,
<<" case find_app_marker(shell) {"/utf8>>,
<<" Ok(marker) -> string.replace(shell, marker, marker <> rendered)"/utf8>>,
<<" Error(Nil) -> shell"/utf8>>,
<<" }"/utf8>>,
<<"}"/utf8>>,
<<""/utf8>>,
<<"fn find_app_marker(shell: String) -> Result(String, Nil) {"/utf8>>,
<<" // Find the element with id=app (any tag, any attributes, any quoting)."/utf8>>,
<<" let id_dq = \"id=\\\"app\\\"\""/utf8>>,
<<" let id_sq = \"id='app'\""/utf8>>,
<<" let id_dq_spaced = \"id = \\\"app\\\"\""/utf8>>,
<<" let id_sq_spaced = \"id = 'app'\""/utf8>>,
<<" let id_attr = case string.contains(shell, id_dq) {"/utf8>>,
<<" True -> Ok(id_dq)"/utf8>>,
<<" False -> case string.contains(shell, id_sq) {"/utf8>>,
<<" True -> Ok(id_sq)"/utf8>>,
<<" False -> case string.contains(shell, id_dq_spaced) {"/utf8>>,
<<" True -> Ok(id_dq_spaced)"/utf8>>,
<<" False -> case string.contains(shell, id_sq_spaced) {"/utf8>>,
<<" True -> Ok(id_sq_spaced)"/utf8>>,
<<" False -> Error(Nil)"/utf8>>,
<<" }"/utf8>>,
<<" }"/utf8>>,
<<" }"/utf8>>,
<<" }"/utf8>>,
<<" use id_attr <- result.try(id_attr)"/utf8>>,
<<" use #(before_id, after_id) <- result.try(string.split_once(shell, id_attr))"/utf8>>,
<<" let reversed_before = string.reverse(before_id)"/utf8>>,
<<" use #(after_lt_rev, _) <- result.try(string.split_once(reversed_before, \"<\"))"/utf8>>,
<<" let tag_start = \"<\" <> string.reverse(after_lt_rev)"/utf8>>,
<<" use #(tag_end, _) <- result.try(string.split_once(after_id, \">\"))"/utf8>>,
<<" Ok(tag_start <> id_attr <> tag_end <> \">\")"/utf8>>,
<<"}"/utf8>>],
<<<<"\n"/utf8, (gleam@string:join(Body, <<"\n"/utf8>>))/binary>>/binary,
"\n"/utf8>>;
_ ->
<<""/utf8>>
end.
-file("src/rally/generator/ssr_handler.gleam", 817).
-spec do_snake_case(list(binary()), binary(), boolean()) -> binary().
do_snake_case(Remaining, Acc, Prev_lower) ->
case Remaining of
[] ->
Acc;
[G | Rest] ->
Lower = string:lowercase(G),
case Lower /= G of
true ->
Sep = case Prev_lower of
true ->
<<"_"/utf8>>;
false ->
<<""/utf8>>
end,
do_snake_case(
Rest,
<<<<Acc/binary, Sep/binary>>/binary, Lower/binary>>,
false
);
false ->
do_snake_case(Rest, <<Acc/binary, G/binary>>, true)
end
end.
-file("src/rally/generator/ssr_handler.gleam", 812).
-spec to_snake_case(binary()) -> binary().
to_snake_case(Name) ->
Graphemes = gleam@string:to_graphemes(Name),
do_snake_case(Graphemes, <<""/utf8>>, false).
-file("src/rally/generator/ssr_handler.gleam", 808).
-spec qualified_wire_name(binary(), binary()) -> binary().
qualified_wire_name(Module_path, Type_name) ->
<<<<(gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>))/binary,
"__"/utf8>>/binary,
(to_snake_case(Type_name))/binary>>.
-file("src/rally/generator/ssr_handler.gleam", 757).
-spec generate_wire_externals(
gleam@option:option(binary()),
gleam@option:option(binary()),
boolean(),
list({rally@types:scanned_route(), rally@types:page_contract()}),
boolean()
) -> binary().
generate_wire_externals(
Wire_module,
Client_context_module,
Has_client_context,
Page_contracts,
Is_json
) ->
case {Wire_module, Is_json} of
{none, _} ->
<<""/utf8>>;
{_, true} ->
<<""/utf8>>;
{{some, Mod}, false} ->
Cc_ext = case {Has_client_context, Client_context_module} of
{true, {some, Cc_mod}} ->
Qual = qualified_wire_name(Cc_mod, <<"ClientContext"/utf8>>),
<<<<<<<<<<<<"\n@external(erlang, \""/utf8, Mod/binary>>/binary,
"\", \"encode_"/utf8>>/binary,
Qual/binary>>/binary,
"\")\nfn wire_encode_"/utf8>>/binary,
Qual/binary>>/binary,
"(value: a) -> b\n"/utf8>>;
{_, _} ->
<<""/utf8>>
end,
Model_exts = begin
_pipe = Page_contracts,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Pair) ->
{Route, Contract} = Pair,
case erlang:element(4, Contract) andalso erlang:element(
7,
Contract
) of
true ->
Qual@1 = qualified_wire_name(
erlang:element(5, Route),
<<"Model"/utf8>>
),
{ok,
<<<<<<<<<<<<"@external(erlang, \""/utf8,
Mod/binary>>/binary,
"\", \"encode_"/utf8>>/binary,
Qual@1/binary>>/binary,
"\")\nfn wire_encode_"/utf8>>/binary,
Qual@1/binary>>/binary,
"(value: a) -> b\n"/utf8>>};
false ->
{error, nil}
end
end
),
_pipe@2 = gleam@list:unique(_pipe@1),
gleam@string:join(_pipe@2, <<"\n"/utf8>>)
end,
<<<<Cc_ext/binary, "\n"/utf8>>/binary, Model_exts/binary>>
end.
-file("src/rally/generator/ssr_handler.gleam", 375).
-spec last_segment(binary()) -> binary().
last_segment(Module_path) ->
case begin
_pipe = gleam@string:split(Module_path, <<"/"/utf8>>),
gleam@list:last(_pipe)
end of
{ok, Seg} ->
Seg;
{error, nil} ->
Module_path
end.
-file("src/rally/generator/ssr_handler.gleam", 340).
-spec import_as(binary(), binary()) -> binary().
import_as(Module_path, Alias) ->
case last_segment(Module_path) =:= Alias of
true ->
<<"import "/utf8, Module_path/binary>>;
false ->
<<<<<<"import "/utf8, Module_path/binary>>/binary, " as "/utf8>>/binary,
Alias/binary>>
end.
-file("src/rally/generator/ssr_handler.gleam", 371).
-spec module_to_alias(binary()) -> binary().
module_to_alias(Module_path) ->
gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>).
-file("src/rally/generator/ssr_handler.gleam", 347).
-spec generate_page_imports(
list({rally@types:scanned_route(), rally@types:page_contract()})
) -> binary().
generate_page_imports(Page_contracts) ->
_pipe = Page_contracts,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Pair) ->
{Route, Contract} = Pair,
case erlang:element(4, Contract) andalso erlang:element(7, Contract) of
true ->
Alias = module_to_alias(erlang:element(5, Route)),
{ok,
<<<<<<"import "/utf8,
(erlang:element(5, Route))/binary>>/binary,
" as "/utf8>>/binary,
Alias/binary>>};
false ->
{error, nil}
end
end
),
_pipe@2 = gleam@list:unique(_pipe@1),
_pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2),
(fun(Imports) -> case Imports of
[] ->
<<""/utf8>>;
_ ->
<<(gleam@string:join(Imports, <<"\n"/utf8>>))/binary,
"\n"/utf8>>
end end)(_pipe@3).
-file("src/rally/generator/ssr_handler.gleam", 382).
-spec generate_layout_imports(
list({rally@types:scanned_route(), rally@types:page_contract()})
) -> binary().
generate_layout_imports(Page_contracts) ->
_pipe = Page_contracts,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Pair) ->
{Route, Contract} = Pair,
case {erlang:element(4, Contract) andalso erlang:element(
7,
Contract
),
erlang:element(6, Route)} of
{true, {some, Layout}} ->
{ok,
<<<<<<"import "/utf8, Layout/binary>>/binary,
" as "/utf8>>/binary,
(module_to_alias(Layout))/binary>>};
{_, _} ->
{error, nil}
end
end
),
_pipe@2 = gleam@list:unique(_pipe@1),
_pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2),
(fun(Imports) -> case Imports of
[] ->
<<""/utf8>>;
_ ->
<<(gleam@string:join(Imports, <<"\n"/utf8>>))/binary,
"\n"/utf8>>
end end)(_pipe@3).
-file("src/rally/generator/ssr_handler.gleam", 747).
-spec route_pattern_for_load(rally@types:scanned_route()) -> binary().
route_pattern_for_load(Route) ->
case erlang:element(4, Route) of
[] ->
<<""/utf8>>;
Params ->
Names = gleam@list:map(Params, fun(P) -> erlang:element(1, P) end),
<<<<"("/utf8, (gleam@string:join(Names, <<", "/utf8>>))/binary>>/binary,
")"/utf8>>
end.
-file("src/rally/generator/ssr_handler.gleam", 404).
-spec generate_load_arms(
list({rally@types:scanned_route(), rally@types:page_contract()}),
boolean(),
boolean(),
boolean(),
binary(),
gleam@option:option(binary()),
gleam@option:option(rally@types:auth_config()),
boolean()
) -> binary().
generate_load_arms(
Page_contracts,
Has_client_context,
Use_session,
Has_from_session,
From_session_module,
Wire_module,
Auth_config,
Is_json
) ->
Auth_module_ref = case Auth_config of
{some, {auth_config, Auth_module}} ->
last_segment(Auth_module);
none ->
<<""/utf8>>
end,
_pipe = Page_contracts,
_pipe@3 = gleam@list:filter_map(
_pipe,
fun(Pair) ->
{Route, Contract} = Pair,
case erlang:element(4, Contract) andalso erlang:element(7, Contract) of
true ->
Alias = module_to_alias(erlang:element(5, Route)),
Pattern = route_pattern_for_load(Route),
Load_args = begin
_pipe@1 = erlang:element(4, Route),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(P) -> erlang:element(1, P) end
),
gleam@string:join(_pipe@2, <<", "/utf8>>)
end,
Has_params = erlang:element(4, Route) /= [],
Has_auth = gleam@option:is_some(Auth_config),
Needs_fs_for_auth = Has_from_session,
Identity_arg = case Has_auth of
true ->
case Has_params of
true ->
<<", server_context, identity"/utf8>>;
false ->
<<"server_context, identity"/utf8>>
end;
false ->
case Has_params of
true ->
<<", server_context"/utf8>>;
false ->
<<"server_context"/utf8>>
end
end,
From_session_call = case Has_auth of
true ->
<<From_session_module/binary,
".from_session(server_context: server_context, session_id: session_id, hostname: hostname, identity: identity)"/utf8>>;
false ->
<<From_session_module/binary,
".from_session(server_context: server_context, session_id: session_id, hostname: hostname)"/utf8>>
end,
Needs_from_session = Use_session orelse (Has_auth andalso Needs_fs_for_auth),
Ctx_init = case Needs_from_session of
true ->
case Has_client_context of
true ->
<<<<" let #(client_context, server_context) = "/utf8,
From_session_call/binary>>/binary,
"\n"/utf8>>;
false ->
<<<<" let #(_, server_context) = "/utf8,
From_session_call/binary>>/binary,
"\n"/utf8>>
end;
false ->
case Has_client_context of
true ->
<<" let #(client_context, _) = client_context.init()\n"/utf8>>;
false ->
<<""/utf8>>
end
end,
View_call = case Has_client_context of
true ->
<<Alias/binary,
".view(client_context, model)"/utf8>>;
false ->
<<Alias/binary, ".view(model)"/utf8>>
end,
View_expr = case {erlang:element(6, Route),
Has_client_context} of
{{some, Layout}, true} ->
Layout_alias = module_to_alias(Layout),
<<<<<<<<<<" let page_view = element.map("/utf8,
View_call/binary>>/binary,
", fn(_) { Nil })\n"/utf8>>/binary,
" let rendered = element.to_string("/utf8>>/binary,
Layout_alias/binary>>/binary,
".layout(client_context, fn(_) { Nil }, page_view))\n"/utf8>>;
{{some, Layout@1}, false} ->
Layout_alias@1 = module_to_alias(Layout@1),
<<<<<<<<<<" let page_view = element.map("/utf8,
View_call/binary>>/binary,
", fn(_) { Nil })\n"/utf8>>/binary,
" let rendered = element.to_string("/utf8>>/binary,
Layout_alias@1/binary>>/binary,
".layout(page_view))\n"/utf8>>;
{none, _} ->
<<<<" let rendered = element.to_string("/utf8,
View_call/binary>>/binary,
")\n"/utf8>>
end,
Ctx_script = case Use_session of
true ->
<<" let ctx_tag = context_script(client_context)\n"/utf8>>;
false ->
<<""/utf8>>
end,
Flags_target = case erlang:element(6, Contract) of
true ->
<<"data"/utf8>>;
false ->
<<"model"/utf8>>
end,
Wire_encode_flags = case {Wire_module, Is_json} of
{{some, _}, false} ->
Qual = qualified_wire_name(
erlang:element(5, Route),
<<"Model"/utf8>>
),
<<<<<<<<<<<<" let "/utf8, Flags_target/binary>>/binary,
" = wire_encode_"/utf8>>/binary,
Qual/binary>>/binary,
"("/utf8>>/binary,
Flags_target/binary>>/binary,
")\n"/utf8>>;
{{some, _}, true} ->
Qual@1 = qualified_wire_name(
erlang:element(5, Route),
<<"Model"/utf8>>
),
<<<<<<<<<<<<" let "/utf8, Flags_target/binary>>/binary,
" = json_codecs.json_encode_"/utf8>>/binary,
Qual@1/binary>>/binary,
"("/utf8>>/binary,
Flags_target/binary>>/binary,
")\n"/utf8>>;
{none, _} ->
<<""/utf8>>
end,
Flags_line = <<<<<<<<Wire_encode_flags/binary,
" let flags = libero_wire.encode_flags("/utf8>>/binary,
Flags_target/binary>>/binary,
")\n"/utf8>>/binary,
" let flags_tag = \"<script>window.__RALLY_FLAGS__='\" <> flags <> \"'</script>\"\n"/utf8>>,
Full_html_line = case Use_session of
true ->
<<" let shell = shell_html()\n let full_html = insert_rendered(shell, rendered)\n <> env.browser_env_script() <> ctx_tag <> flags_tag\n"/utf8>>;
false ->
<<" let shell = shell_html()\n let full_html = insert_rendered(shell, rendered)\n <> env.browser_env_script() <> flags_tag\n"/utf8>>
end,
Render_body = <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ctx_init/binary,
" let "/utf8>>/binary,
Flags_target/binary>>/binary,
" = "/utf8>>/binary,
Alias/binary>>/binary,
".load("/utf8>>/binary,
Load_args/binary>>/binary,
Identity_arg/binary>>/binary,
")\n"/utf8>>/binary,
(case {erlang:element(
6,
Contract
),
Has_client_context} of
{true, true} ->
<<<<" let #(model, _) = "/utf8,
Alias/binary>>/binary,
".init_loaded(client_context, data)\n"/utf8>>;
{true, false} ->
<<<<" let #(model, _) = "/utf8,
Alias/binary>>/binary,
".init_loaded(data)\n"/utf8>>;
{false, _} ->
<<""/utf8>>
end)/binary>>/binary,
" "/utf8>>/binary,
View_expr/binary>>/binary,
Ctx_script/binary>>/binary,
Flags_line/binary>>/binary,
Full_html_line/binary>>/binary,
" response.new(200)\n"/utf8>>/binary,
" |> response.set_header(\"content-type\", \"text/html\")\n"/utf8>>/binary,
" |> response.set_body(mist.Bytes(bytes_tree.from_string(full_html)))\n"/utf8>>,
Arm_body = case Has_auth of
true ->
Resolve_open = <<<<<<<<<<<<" case "/utf8,
Auth_module_ref/binary>>/binary,
".resolve(server_context, session_id) {\n"/utf8>>/binary,
" Error(Nil) ->\n"/utf8>>/binary,
" response.new(500)\n"/utf8>>/binary,
" |> response.set_body(mist.Bytes(bytes_tree.from_string(\"Auth service unavailable\")))\n"/utf8>>/binary,
" Ok(identity) -> {\n"/utf8>>,
Is_auth_check = case erlang:element(15, Contract) of
true ->
<<<<<<<<<<<<<<<<" case "/utf8,
Auth_module_ref/binary>>/binary,
".is_authenticated(identity) {\n"/utf8>>/binary,
" False -> response.new(302)\n"/utf8>>/binary,
" |> response.set_header(\"location\", "/utf8>>/binary,
Auth_module_ref/binary>>/binary,
".redirect_url)\n"/utf8>>/binary,
" |> response.set_body(mist.Bytes(bytes_tree.from_string(\"\")))\n"/utf8>>/binary,
" True -> {\n"/utf8>>;
false ->
<<""/utf8>>
end,
Authorize_check = case erlang:element(16, Contract) of
true ->
<<<<<<<<<<" case "/utf8,
Alias/binary>>/binary,
".authorize(server_context, identity) {\n"/utf8>>/binary,
" False -> response.new(403)\n"/utf8>>/binary,
" |> response.set_body(mist.Bytes(bytes_tree.from_string(\"Forbidden\")))\n"/utf8>>/binary,
" True -> {\n"/utf8>>;
false ->
<<""/utf8>>
end,
Model_var = case erlang:element(6, Contract) of
true ->
<<"model"/utf8>>;
false ->
<<"data"/utf8>>
end,
View_call_auth = case Has_client_context of
true ->
<<<<<<Alias/binary,
".view(client_context, "/utf8>>/binary,
Model_var/binary>>/binary,
")"/utf8>>;
false ->
<<<<<<Alias/binary, ".view("/utf8>>/binary,
Model_var/binary>>/binary,
")"/utf8>>
end,
View_expr_auth = case {erlang:element(6, Route),
Has_client_context} of
{{some, Layout@2}, true} ->
Layout_alias@2 = module_to_alias(Layout@2),
<<<<<<<<<<" let page_view = element.map("/utf8,
View_call_auth/binary>>/binary,
", fn(_) { Nil })\n"/utf8>>/binary,
" let rendered = element.to_string("/utf8>>/binary,
Layout_alias@2/binary>>/binary,
".layout(client_context, fn(_) { Nil }, page_view))\n"/utf8>>;
{{some, Layout@3}, false} ->
Layout_alias@3 = module_to_alias(Layout@3),
<<<<<<<<<<" let page_view = element.map("/utf8,
View_call_auth/binary>>/binary,
", fn(_) { Nil })\n"/utf8>>/binary,
" let rendered = element.to_string("/utf8>>/binary,
Layout_alias@3/binary>>/binary,
".layout(page_view))\n"/utf8>>;
{none, _} ->
<<<<" let rendered = element.to_string("/utf8,
View_call_auth/binary>>/binary,
")\n"/utf8>>
end,
Wire_encode_flags_auth = case {Wire_module, Is_json} of
{{some, _}, false} ->
Qual@2 = qualified_wire_name(
erlang:element(5, Route),
<<"Model"/utf8>>
),
<<<<<<<<<<<<" let "/utf8,
Model_var/binary>>/binary,
" = wire_encode_"/utf8>>/binary,
Qual@2/binary>>/binary,
"("/utf8>>/binary,
Model_var/binary>>/binary,
")\n"/utf8>>;
{{some, _}, true} ->
Qual@3 = qualified_wire_name(
erlang:element(5, Route),
<<"Model"/utf8>>
),
<<<<<<<<<<<<" let "/utf8,
Model_var/binary>>/binary,
" = json_codecs.json_encode_"/utf8>>/binary,
Qual@3/binary>>/binary,
"("/utf8>>/binary,
Model_var/binary>>/binary,
")\n"/utf8>>;
{none, _} ->
<<""/utf8>>
end,
Flags_line_auth = <<<<<<<<Wire_encode_flags_auth/binary,
" let flags = libero_wire.encode_flags("/utf8>>/binary,
Model_var/binary>>/binary,
")\n"/utf8>>/binary,
" let flags_tag = \"<script>window.__RALLY_FLAGS__='\" <> flags <> \"'</script>\"\n"/utf8>>,
Load_result_block = <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ctx_init/binary,
" case "/utf8>>/binary,
Alias/binary>>/binary,
".load("/utf8>>/binary,
Load_args/binary>>/binary,
Identity_arg/binary>>/binary,
") {\n"/utf8>>/binary,
" rally_auth.Page(data, cookies) -> {\n"/utf8>>/binary,
(case {erlang:element(
6,
Contract
),
Has_client_context} of
{true,
true} ->
<<<<" let #(model, _) = "/utf8,
Alias/binary>>/binary,
".init_loaded(client_context, data)\n"/utf8>>;
{true,
false} ->
<<<<" let #(model, _) = "/utf8,
Alias/binary>>/binary,
".init_loaded(data)\n"/utf8>>;
{false,
_} ->
<<""/utf8>>
end)/binary>>/binary,
" "/utf8>>/binary,
View_expr_auth/binary>>/binary,
Ctx_script/binary>>/binary,
Flags_line_auth/binary>>/binary,
Full_html_line/binary>>/binary,
" apply_cookies(\n"/utf8>>/binary,
" response.new(200)\n"/utf8>>/binary,
" |> response.set_header(\"content-type\", \"text/html\")\n"/utf8>>/binary,
" |> response.set_body(mist.Bytes(bytes_tree.from_string(full_html))),\n"/utf8>>/binary,
" cookies,\n"/utf8>>/binary,
" )\n"/utf8>>/binary,
" }\n"/utf8>>/binary,
" rally_auth.Redirect(url, cookies) ->\n"/utf8>>/binary,
" apply_cookies(\n"/utf8>>/binary,
" response.new(302)\n"/utf8>>/binary,
" |> response.set_header(\"location\", url)\n"/utf8>>/binary,
" |> response.set_body(mist.Bytes(bytes_tree.from_string(\"\"))),\n"/utf8>>/binary,
" cookies,\n"/utf8>>/binary,
" )\n"/utf8>>/binary,
" }\n"/utf8>>,
Close_authorize = case erlang:element(16, Contract) of
true ->
<<" }\n }\n"/utf8>>;
false ->
<<""/utf8>>
end,
Close_is_auth = case erlang:element(15, Contract) of
true ->
<<" }\n }\n"/utf8>>;
false ->
<<""/utf8>>
end,
Close_resolve = <<" }\n }\n"/utf8>>,
<<<<<<<<<<<<Resolve_open/binary,
Is_auth_check/binary>>/binary,
Authorize_check/binary>>/binary,
Load_result_block/binary>>/binary,
Close_authorize/binary>>/binary,
Close_is_auth/binary>>/binary,
Close_resolve/binary>>;
false ->
Render_body
end,
{ok,
<<<<<<<<<<" router."/utf8,
(erlang:element(3, Route))/binary>>/binary,
Pattern/binary>>/binary,
" -> {\n"/utf8>>/binary,
Arm_body/binary>>/binary,
" }"/utf8>>};
false ->
{error, nil}
end
end
),
gleam@string:join(_pipe@3, <<"\n"/utf8>>).
-file("src/rally/generator/ssr_handler.gleam", 16).
-spec generate(
list({rally@types:scanned_route(), rally@types:page_contract()}),
boolean(),
boolean(),
binary(),
binary(),
binary(),
binary(),
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(rally@types:auth_config()),
binary(),
binary()
) -> binary().
generate(
Page_contracts,
Has_client_context,
Has_from_session,
From_session_module,
Router_module,
Shell_html,
Atoms_module,
Wire_module,
Client_context_module,
Auth_config,
Wire_import_module,
Protocol
) ->
Use_session = Has_client_context andalso Has_from_session,
Is_json = Protocol =:= <<"json"/utf8>>,
Json_codecs_module = case Is_json of
true ->
gleam@string:replace(
Wire_import_module,
<<"/protocol_wire"/utf8>>,
<<"/json_codecs"/utf8>>
);
false ->
<<""/utf8>>
end,
From_session_ref = last_segment(From_session_module),
Has_auth = gleam@option:is_some(Auth_config),
Auth_module_ref = case Auth_config of
{some, {auth_config, Auth_module}} ->
last_segment(Auth_module);
none ->
<<""/utf8>>
end,
Load_arms = generate_load_arms(
Page_contracts,
Has_client_context,
Use_session,
Has_from_session,
From_session_ref,
Wire_module,
Auth_config,
Is_json
),
Has_load_pages = Load_arms /= <<""/utf8>>,
All_routes_have_load = gleam@list:all(
Page_contracts,
fun(Pair) ->
{_, Contract} = Pair,
erlang:element(4, Contract) andalso erlang:element(7, Contract)
end
),
Layout_imports = generate_layout_imports(Page_contracts),
Page_imports = generate_page_imports(Page_contracts),
Needs_server_context = Use_session orelse Has_load_pages,
Needs_from_session_import = Use_session orelse (Has_auth andalso Has_from_session),
Needs_codec = Use_session orelse Has_load_pages,
Base_imports = <<<<<<<<"// Generated by Rally — do not edit.\n\nimport gleam/bytes_tree\nimport gleam/http/response\nimport mist.{type ResponseData}\n"/utf8,
(import_as(Router_module, <<"router"/utf8>>))/binary>>/binary,
"\nimport rally_runtime/env\n\n@external(erlang, \""/utf8>>/binary,
Atoms_module/binary>>/binary,
"\", \"ensure\")\nfn ensure_atoms() -> Nil\n"/utf8>>,
Server_imports = case Needs_server_context of
true ->
<<"import server_context.{type ServerContext}\n"/utf8,
(case Needs_from_session_import andalso (From_session_module /= <<"server_context"/utf8>>) of
true ->
<<(import_as(From_session_module, From_session_ref))/binary,
"\n"/utf8>>;
false ->
<<""/utf8>>
end)/binary>>;
false ->
<<""/utf8>>
end,
Client_context_imports = case {Has_client_context, Client_context_module} of
{true, {some, Mod}} ->
<<(import_as(Mod, <<"client_context"/utf8>>))/binary, "\n"/utf8>>;
{_, _} ->
<<""/utf8>>
end,
Codec_imports = case Needs_codec of
true ->
<<(import_as(Wire_import_module, <<"libero_wire"/utf8>>))/binary,
"\n"/utf8>>;
false ->
<<""/utf8>>
end,
Json_codec_imports = case Is_json andalso Needs_codec of
true ->
<<(import_as(Json_codecs_module, <<"json_codecs"/utf8>>))/binary,
"\n"/utf8>>;
false ->
<<""/utf8>>
end,
Load_page_imports = case Has_load_pages of
true ->
<<"import gleam/result\nimport gleam/string\nimport lustre/element\n"/utf8>>;
false ->
<<""/utf8>>
end,
Auth_imports = case Auth_config of
{some, {auth_config, Auth_module@1}} ->
<<(import_as(Auth_module@1, Auth_module_ref))/binary,
(case Has_load_pages of
true ->
<<"\nimport rally_runtime/auth as rally_auth\nimport gleam/int\nimport gleam/list\n"/utf8>>;
false ->
<<"\n"/utf8>>
end)/binary>>;
_ ->
<<""/utf8>>
end,
Wire_externals = generate_wire_externals(
Wire_module,
Client_context_module,
Has_client_context,
Page_contracts,
Is_json
),
Header = <<<<<<<<<<<<<<<<<<Base_imports/binary, Server_imports/binary>>/binary,
Client_context_imports/binary>>/binary,
Codec_imports/binary>>/binary,
Json_codec_imports/binary>>/binary,
Load_page_imports/binary>>/binary,
Auth_imports/binary>>/binary,
Layout_imports/binary>>/binary,
Page_imports/binary>>/binary,
Wire_externals/binary>>,
Fn_params = case Needs_server_context of
true ->
<<"
pub fn handle_request(
route route: router.Route,
server_context server_context: ServerContext,
session_id session_id: String,
hostname hostname: String,
) -> response.Response(ResponseData) {"/utf8>>;
false ->
<<"
pub fn handle_request(
_route: router.Route,
) -> response.Response(ResponseData) {"/utf8>>
end,
Cc_encode_line = case {Wire_module, Client_context_module, Is_json} of
{{some, _}, {some, Cc_mod}, false} ->
Qual = qualified_wire_name(Cc_mod, <<"ClientContext"/utf8>>),
<<<<" let client_context = wire_encode_"/utf8, Qual/binary>>/binary,
"(client_context)\n"/utf8>>;
{_, {some, Cc_mod@1}, true} ->
Qual@1 = qualified_wire_name(Cc_mod@1, <<"ClientContext"/utf8>>),
<<<<" let client_context = json_codecs.json_encode_"/utf8,
Qual@1/binary>>/binary,
"(client_context)\n"/utf8>>;
{_, _, _} ->
<<""/utf8>>
end,
Ctx_script = case Use_session of
true ->
<<<<"
fn context_script(client_context: client_context.ClientContext) -> String {
"/utf8,
Cc_encode_line/binary>>/binary,
" let encoded = libero_wire.encode_flags(client_context)
\"<script>window.__RALLY_CLIENT_CONTEXT__='\" <> encoded <> \"'</script>\"
}
"/utf8>>;
false ->
<<""/utf8>>
end,
Shell_call = case Use_session of
true ->
<<"serve_html_shell(server_context: server_context, session_id: session_id, hostname: hostname)"/utf8>>;
false ->
<<"serve_html_shell()"/utf8>>
end,
Fn_body = case Has_load_pages of
true ->
Fallback = case All_routes_have_load of
true ->
<<""/utf8>>;
false ->
<<"\n _ -> "/utf8, Shell_call/binary>>
end,
<<<<<<<<<<Fn_params/binary,
"
ensure_atoms()
case route {\n"/utf8>>/binary,
Load_arms/binary>>/binary,
"
router.NotFound(_) ->
response.new(404)
|> response.set_body(mist.Bytes(bytes_tree.from_string(\"Not found\")))"/utf8>>/binary,
Fallback/binary>>/binary,
"
}
}"/utf8>>;
false ->
<<<<<<Fn_params/binary, "
ensure_atoms()
"/utf8>>/binary,
Shell_call/binary>>/binary,
"
}"/utf8>>
end,
Dark_mode_script = <<"<script>(function(){var c=document.cookie;if(c.includes('__rally_dark_mode=1')||(!c.includes('__rally_dark_mode=')&&window.matchMedia('(prefers-color-scheme:dark)').matches)){document.documentElement.classList.add('dark')}})()</script>"/utf8>>,
Shell_with_dark = gleam@string:replace(
Shell_html,
<<"</head>"/utf8>>,
<<Dark_mode_script/binary, "</head>"/utf8>>
),
Escaped_shell = begin
_pipe = Shell_with_dark,
_pipe@1 = gleam@string:replace(_pipe, <<"\\"/utf8>>, <<"\\\\"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"\\\""/utf8>>),
gleam@string:replace(_pipe@2, <<"\n"/utf8>>, <<"\\n"/utf8>>)
end,
Needs_shell_fn = case Has_load_pages of
true ->
not All_routes_have_load;
false ->
true
end,
Shell_fn = case Needs_shell_fn of
false ->
<<""/utf8>>;
true ->
case {Use_session, Has_auth} of
{true, true} ->
<<<<<<<<<<<<"
fn serve_html_shell(server_context server_context: ServerContext, session_id session_id: String, hostname hostname: String) -> response.Response(ResponseData) {
case "/utf8,
Auth_module_ref/binary>>/binary,
".resolve(server_context, session_id) {
Error(Nil) ->
response.new(500)
|> response.set_body(mist.Bytes(bytes_tree.from_string(\"Auth service unavailable\")))
Ok(identity) -> {
let #(client_context, _) = "/utf8>>/binary,
From_session_ref/binary>>/binary,
".from_session(server_context: server_context, session_id: session_id, hostname: hostname, identity: identity)
let html = \""/utf8>>/binary,
Escaped_shell/binary>>/binary,
"\" <> env.browser_env_script() <> context_script(client_context)
response.new(200)
|> response.set_header(\"content-type\", \"text/html\")
|> response.set_body(mist.Bytes(bytes_tree.from_string(html)))
}
}
}
"/utf8>>;
{true, false} ->
<<<<<<<<"
fn serve_html_shell(server_context server_context: ServerContext, session_id session_id: String, hostname hostname: String) -> response.Response(ResponseData) {
let #(client_context, _) = "/utf8,
From_session_ref/binary>>/binary,
".from_session(server_context: server_context, session_id: session_id, hostname: hostname)
let html = \""/utf8>>/binary,
Escaped_shell/binary>>/binary,
"\" <> env.browser_env_script() <> context_script(client_context)
response.new(200)
|> response.set_header(\"content-type\", \"text/html\")
|> response.set_body(mist.Bytes(bytes_tree.from_string(html)))
}
"/utf8>>;
{false, _} ->
<<<<"
fn serve_html_shell() -> response.Response(ResponseData) {
let html = \""/utf8,
Escaped_shell/binary>>/binary,
"\" <> env.browser_env_script()
response.new(200)
|> response.set_header(\"content-type\", \"text/html\")
|> response.set_body(mist.Bytes(bytes_tree.from_string(html)))
}
"/utf8>>
end
end,
Shell_html_fn = case Has_load_pages of
true ->
<<<<"
fn shell_html() -> String {
\""/utf8, Escaped_shell/binary>>/binary,
"\"
}
"/utf8>>;
false ->
<<""/utf8>>
end,
Cookies_helper = case Has_auth andalso Has_load_pages of
true ->
<<"
fn apply_cookies(resp: response.Response(ResponseData), cookies: List(rally_auth.Cookie)) -> response.Response(ResponseData) {
list.fold(cookies, resp, fn(resp, cookie) {
case cookie {
rally_auth.SetCookie(name, value, max_age) ->
response.prepend_header(resp, \"set-cookie\",
name <> \"=\" <> value <> \"; Path=/; HttpOnly; SameSite=Lax; Max-Age=\" <> int.to_string(max_age))
rally_auth.ClearCookie(name) ->
response.prepend_header(resp, \"set-cookie\",
name <> \"=; Path=/; HttpOnly; Max-Age=0\")
}
})
}
"/utf8>>;
false ->
<<""/utf8>>
end,
<<<<<<<<<<<<Header/binary, Fn_body/binary>>/binary, Ctx_script/binary>>/binary,
Shell_fn/binary>>/binary,
Shell_html_fn/binary>>/binary,
Cookies_helper/binary>>/binary,
(marker_helper(Has_load_pages))/binary>>.