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:
parent
401aec67d0
commit
6790eec12c
5 changed files with 5 additions and 14 deletions
1
src/exec/fixtures/mod.rs
Normal file
1
src/exec/fixtures/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod scripting;
|
||||
236
src/exec/fixtures/scripting.rs
Normal file
236
src/exec/fixtures/scripting.rs
Normal 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"
|
||||
"#;
|
||||
344
src/exec/fixtures/sql-client-integration.js
Normal file
344
src/exec/fixtures/sql-client-integration.js
Normal 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;
|
||||
|
|
@ -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});",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue