refactor: create fixtures module

Prepare for adding a second radically different fixture by creating a
module to hold the different fixtures. When I only had one fixture it
was ok to have the module be a single file.
This commit is contained in:
YetAnotherMinion 2022-01-08 15:58:32 +00:00 committed by nobody
commit 6790eec12c
Signed by: GrocerPublishAgent
GPG key ID: D460CD54A9E3AB86
5 changed files with 5 additions and 14 deletions

1
src/exec/fixtures/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod scripting;

View file

@ -0,0 +1,236 @@
use crate::exec::{InputType, OutputType};
use std::fmt::Display;
pub(crate) fn generate<S: AsRef<str>>(
source_checksum: u64,
target_module: S,
target_function: S,
input: Option<InputType>,
output: OutputType,
) -> (String, String) {
// The generated fixture Elm files are around 3007 bytes which includes the import of the
// target module and function
let mut buffer =
String::with_capacity(3000 + target_module.as_ref().len() + target_function.as_ref().len());
let module_name = format!("Gen{:020}", source_checksum);
use std::fmt::Write;
write!(buffer, "port module {} exposing (main)\n", &module_name).unwrap();
buffer.push_str(GENERATOR_IMPORTS);
buffer.push_str("-- START CUSTOMIZED PART\n");
write!(buffer, "import {}\n\n", target_module.as_ref()).unwrap();
match output {
OutputType::String => buffer.push_str("encodeOutput = encodeString\n"),
OutputType::Value => buffer.push_str("encodeOutput = encodeJson\n"),
OutputType::Bytes => buffer.push_str("encodeOutput = encodeBytes\n"),
OutputType::Html => buffer.push_str("encodeOutput = encodeHtml\n"),
}
// if the input type is none then we don't have to generate an apply, and the port input type
// is ()
match input {
None => {
buffer.push_str(&zero_arg_apply(
target_module.as_ref(),
target_function.as_ref(),
));
}
Some(InputType::Value) => {
buffer.push_str("decodeInput = decodeValue\n");
buffer.push_str(&one_arg_apply(
target_module.as_ref(),
target_function.as_ref(),
));
}
Some(InputType::Bytes) => {
buffer.push_str("decodeInput = decodeBytes\n");
buffer.push_str(&one_arg_apply(
target_module.as_ref(),
target_function.as_ref(),
));
}
Some(InputType::String) => {
buffer.push_str("decodeInput = decodeString\n");
buffer.push_str(&one_arg_apply(
target_module.as_ref(),
target_function.as_ref(),
));
}
}
buffer.push_str("-- END CUSTOMIZED PART\n");
buffer.push_str(GENERATOR_BODY);
(module_name, buffer)
}
// port module Generator exposing (main)
const GENERATOR_IMPORTS: &'static str = r#"
import Bytes exposing (Bytes)
import ElmHtml.InternalTypes exposing (decodeElmHtml)
import ElmHtml.ToString exposing (nodeToStringWithOptions, defaultFormatOptions)
import Html exposing (Html)
import Json.Decode as D
import Json.Encode as E
import Platform
"#;
fn zero_arg_apply<S: Display>(target_module: S, function: S) -> String {
format!(
r#"
apply : E.Value -> Cmd Msg
apply _ =
{target_module}.{function}
|> encodeOutput
|> onOutput
"#,
target_module = target_module,
function = function,
)
}
// The user just has to alias the decodeInput and encodeOutput
fn one_arg_apply<S: Display>(target_module: S, function: S) -> String {
format!(
r#"
apply : E.Value -> Cmd Msg
apply value =
case decodeInput value of
Ok input ->
input
|> {target_module}.{function}
|> encodeOutput
|> onOutput
Err error ->
error
|> D.errorToString
|> encodeFailure
|> onOutput
"#,
target_module = target_module,
function = function,
)
}
const GENERATOR_BODY: &'static str = r#"
-- MAIN
main =
Platform.worker
{ init = init
, update = update
, subscriptions = subscriptions
}
-- PORTS
port onInput : (E.Value -> msg) -> Sub msg
port onOutput : E.Value -> Cmd msg
-- MODEL
type alias Model = ()
init : () -> (Model, Cmd Msg)
init _ =
((), Cmd.none)
-- UPDATE
type Msg
= Input E.Value
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Input value ->
let
cmd =
apply value
in
(model, cmd)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions _ =
onInput Input
-- DECODERS
decodeBytes : E.Value -> Result D.Error Bytes
decodeBytes value =
D.decodeValue decodeBytesHelp value
decodeBytesHelp : D.Decoder Bytes
decodeBytesHelp =
D.fail "REPLACE_ME_WITH_BYTES_DECODER"
decodeString : E.Value -> Result D.Error String
decodeString value =
D.decodeValue D.string value
-- ENCODERS
encodeFailure : String -> E.Value
encodeFailure msg =
E.object
[ ("ctor", E.string "Err")
, ("a", E.string msg)
]
encodeSuccess : E.Value -> E.Value
encodeSuccess value =
E.object
[ ("ctor", E.string "Ok")
, ("a", value)
]
encodeJson : E.Value -> E.Value
encodeJson =
encodeSuccess
encodeBytes : Bytes -> E.Value
encodeBytes bytes =
bytes
|> encodeBytesHelp
|> encodeSuccess
encodeBytesHelp : Bytes -> E.Value
encodeBytesHelp bytes =
E.string "REPLACE_ME_WITH_BYTES_ENCODER"
encodeString : String -> E.Value
encodeString s =
encodeSuccess (E.string s)
encodeHtml : Html msg -> E.Value
encodeHtml node =
let
options =
{ defaultFormatOptions | newLines = True, indent = 2 }
in
case
D.decodeString
(decodeElmHtml (\taggers eventHandler -> D.succeed ()))
(asJsonString node)
of
Ok elmHtml ->
elmHtml
|> nodeToStringWithOptions options
|> encodeString
Err error ->
error
|> D.errorToString
|> encodeFailure
asJsonString : Html msg -> String
asJsonString x = "REPLACE_ME_WITH_JSON_STRINGIFY"
"#;

View file

@ -0,0 +1,344 @@
// CORE QUERIES
function __Debug_print(object) {
Deno.core.print(JSON.stringify(object));
Deno.core.print("\n");
}
function __Debug_print_slots(values) {
var len = values.length;
for (var i = 0; i < len; i++) {
Deno.core.print([" ", i, ": ", JSON.stringify(values[i]), "\n"].join(""));
}
}
function __Debug_assert(expr) {
if (!expr) {
throw new Error("debug assert");
}
}
function _Query_succeed(value)
{
return {
$: 0,
a: value
};
};
var _Query_fetchOptional = F3(function(sql, args, decoder)
{
return {
$: 1,
b: sql,
c: args,
d: { $: 0, a: decoder }
};
});
var _Query_fetchOne = F3(function(sql, args, decoder)
{
return {
$: 2,
b: sql,
c: args,
d: { $: 1, a: decoder }
};
});
var _Query_fetchAll = F3(function(sql, args, decoder)
{
return {
$: 3,
b: sql,
c: args,
d: { $: 2, a: decoder }
};
});
function _Query_mapMany(f, queries)
{
return {
$: 4,
f: f,
g: queries
};
}
var _Query_andThen = F2(
function(callback, query) {
return {
$: 5,
e: query,
h: callback
};
});
var _Query_map1 = F2(function(f, q1)
{
return _Query_mapMany(f, [q1]);
});
var _Query_map2 = F3(function(f, q1, q2)
{
return _Query_mapMany(f, [q1, q2]);
});
var _Query_map3 = F4(function(f, q1, q2, q3)
{
return _Query_mapMany(f, [q1, q2, q3]);
});
var _Query_map4 = F5(function(f, q1, q2, q3, q4)
{
return _Query_mapMany(f, [q1, q2, q3, q4]);
});
var _Query_map5 = F6(function(f, q1, q2, q3, q4, q5)
{
return _Query_mapMany(f, [q1, q2, q3, q4, q5]);
});
var _Query_map6 = F7(function(f, q1, q2, q3, q4, q5, q6)
{
return _Query_mapMany(f, [q1, q2, q3, q4, q5, q6]);
});
var _Query_map7 = F8(function(f, q1, q2, q3, q4, q5, q6, q7)
{
return _Query_mapMany(f, [q1, q2, q3, q4, q5, q6, q7]);
});
var _Query_map8 = F9(function(f, q1, q2, q3, q4, q5, q6, q7, q8)
{
return _Query_mapMany(f, [q1, q2, q3, q4, q5, q6, q7, q8]);
});
// RUN
function _Query_runDecoder(decoder, sql, xs)
{
switch (decoder.$) {
case 0:
if (xs.length === 0) {
return $elm$core$Result$Ok($elm$core$Maybe$Nothing);
}
var result = _Json_runOnString.f(decoder.a, xs[0]);
if (!$elm$core$Result$isOk(result))
{
return $elm$core$Result$Err(
A3(
$author$project$Astrid$Query$Decode,
sql,
0,
result.a
)
);
}
return $elm$core$Result$Ok($elm$core$Maybe$Just(result.a));
case 1:
if (xs.length === 0) {
return $elm$core$Result$Err($author$project$Astrid$Query$NotFound(sql));
}
var result = _Json_runOnString.f(decoder.a, xs[0]);
if (!$elm$core$Result$isOk(result))
{
return $elm$core$Result$Err(
A3(
$author$project$Astrid$Query$Decode,
sql,
0,
result.a
)
);
}
return result;
case 2:
var len = xs.length;
var array = new Array(len);
for (var i = 0; i < len; i++)
{
var string = xs[i];
try
{
var value = JSON.parse(string);
__Debug_print("parsed the json");
__Debug_print(value);
__Debug_print(decoder);
var result = _Json_runHelp(decoder.a, value);
__Debug_print("result of parsing the json");
__Debug_print(result);
if (!$elm$core$Result$isOk(result))
{
return $elm$core$Result$Err(
A3(
$author$project$Astrid$Query$Decode,
sql,
i,
result.a
)
);
}
array[i] = result.a;
}
catch (e)
{
return $elm$core$Result$Err(
A3(
$author$project$Astrid$Query$Decode,
sql,
i,
A2(
$elm$json$Json$Decode$Failure,
'This is not valid JSON! ' + e.message, _Json_wrap(string)
)
)
);
}
}
return $elm$core$Result$Ok(_Json_toElmArray(array));
}
}
var _Query_execute = function(query)
{
// queries: Array (Int, Query a)
// values: Array (Maybe a)
// callbacks: Array (Int, Fn: * -> a)
var queries = new Array;
var values = new Array;
var callbacks = new Array;
var statements = new Array;
var decoders = new Array;
queries.push({slot: 0, query: query});
values.push($elm$core$Maybe$Nothing);
while (true) {
var x;
while(x = queries.pop()) {
var slot = x.slot;
var q = x.query;
//__Debug_print({"the result of this query goes in slot": slot});
//__Debug_print({"query": q ? q : "missing" });
//__Debug_print_slots(values);
switch (q.$) {
case 0:
values[slot] = $elm$core$Maybe$Just(q.a);
break;
case 1:
case 2:
case 3:
var moreThanOneRow = (q.$ === 3)
var sql = q.b;
var bindParameters = _List_toArray(q.c);
var decoder = q.d;
statements.push([moreThanOneRow, sql, bindParameters]);
decoders.push({slot: slot, decoder: decoder});
break;
case 4:
callbacks.push({ $:'Map', a: q.f, slot: slot })
// Array.prototype.push.apply(queries, q.g);
var len = q.g.length;
for (var i = 0; i < len; i++) {
queries.push({slot: values.length, query: q.g[i]})
values.push($elm$core$Maybe$Nothing);
}
break;
case 5:
callbacks.push({ $:'AndThen', a: q.h, slot: slot })
queries.push({slot: values.length, query: q.e})
values.push($elm$core$Maybe$Nothing);
break;
}
}
__Debug_print("-----------------------");
if (statements.length > 0) {
var queryResult = Deno.core.opSync(
'op_starmelon_batch_queries',
statements,
);
// I am assuming here that the Rust code is serializing the same
// structure that the Elm compiler thinks we have.
if (!$elm$core$Result$isOk(queryResult)) {
return queryResult
}
var results = queryResult.a;
var len = results.length;
for (var i = 0; i < len; i++) {
var { decoder, slot } = decoders[i];
var result = _Query_runDecoder(decoder, statements[i][1], results[i])
if (!$elm$core$Result$isOk(result))
{
return result
}
values[slot] = $elm$core$Maybe$Just(result.a);
}
statements.length = 0;
decoders.length = 0;
}
__Debug_print({"processing callbacks stack": callbacks});
reduce:
while(callbacks.length > 0) {
var last = callbacks[callbacks.length - 1];
switch (last.$) {
case 'Map':
var arity = last.a.a ? last.a.a : 1;
var slot = last.slot;
var len = values.length;
if (len < arity + 1) {
// This implies that queries.length > 0 because we must
// have a way to generate the missing value(s) to call
// this function.
break reduce;
}
// Check if all the values are ready
var args = new Array;
for(var i = len - arity; i < len; i++) {
if (values[i].$ === 'Nothing') {
break reduce;
}
args.push(values[i].a);
}
callbacks.pop();
// Directly call the wrapped Elm function since we know all
// the arguments
var fun = last.a.f ? last.a.f : last.a;
values.length = values.length - arity;
values[slot] = $elm$core$Maybe$Just(fun.apply(null, args));
break;
case 'AndThen':
callbacks.pop();
var fun = last.a;
var slot = last.slot;
__Debug_assert(values.length > 0);
var maybeValue = values.pop();
__Debug_assert(maybeValue.$ === 'Just');
queries.push({slot: slot, query: fun(maybeValue.a)})
}
}
if (queries.length == 0 && callbacks.length == 0) {
__Debug_assert(values.length === 1);
__Debug_assert(values[0].$ === 'Just');
return $elm$core$Result$Ok(values.pop().a)
}
}
};
var $author$project$Astrid$Query$execute = _Query_execute;
var $author$project$Astrid$Query$andThen = _Query_andThen;

View file

@ -1,5 +1,4 @@
use crate::elm;
use crate::fixture;
use crate::reporting::{CompilerError, InterpreterError, Problem, SetupError, TypeError};
use crate::PortableHash;
use deno_core::futures::StreamExt;
@ -19,6 +18,8 @@ use std::time::Instant;
use tokio;
use tracing::{info_span, Instrument};
mod fixtures;
#[derive(Serialize)]
#[serde(tag = "$")]
pub(crate) enum ElmResult<T, E> {
@ -104,7 +105,7 @@ pub(crate) fn exec(
// step 7 create an Elm fixture file to run our function
let span = info_span!("create Elm fixture files");
let timing_guard = span.enter();
let (gen_module_name, source) = fixture::generate(
let (gen_module_name, source) = fixtures::scripting::generate(
source_checksum,
entrypoint.0.module.clone(),
entrypoint.1.clone(),
@ -194,7 +195,7 @@ pub(crate) fn exec(
final_script = final_script
.replace(
"var $author$project$Astrid$Query$execute = function (query) {\n\treturn $author$project$Astrid$Query$dummyExecute;\n};",
include_str!("../fixture/query.js"),
include_str!("fixtures/sql-client-integration.js"),
)
.replace(
"var $author$project$Astrid$Query$fetch = F3(\n\tfunction (sql, parameters, decoder) {\n\t\treturn $author$project$Astrid$Query$Dummy;\n\t});",