Skip to content

Commit

Permalink
Merge pull request #8 from ty-ras/issue/7-add-helper-for-single-row-q…
Browse files Browse the repository at this point in the history
…ueries

#7 Adding helper for single-row queries.
  • Loading branch information
stazz authored Aug 19, 2023
2 parents 12bcf9d + 04048bf commit 6d2aad8
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
2 changes: 1 addition & 1 deletion typed-sql/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ty-ras-extras/typed-sql-runtypes",
"version": "2.0.0",
"version": "2.1.0",
"author": {
"name": "Stanislav Muhametsin",
"email": "[email protected]",
Expand Down
27 changes: 27 additions & 0 deletions typed-sql/src/__test__/output.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,30 @@ test("Validate that validateRows invokes given validation", async (c) => {
instanceOf: errors.SQLQueryOutputValidationError,
});
});

test("Validate that validateOneRow invokes given validation", async (c) => {
c.plan(3);
const firstQueryResult = ["returnedRow"];
const secondQueryResult = ["one", "two"];
const thirdQueryResult = "bad-query";
const { usingMockedClient } = common.createMockedClientProvider([
firstQueryResult,
secondQueryResult,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
thirdQueryResult as any,
]);
const executor = spec.validateOneRow(
input.prepareSQL`SELECT 1`(usingMockedClient),
// Validator for single row, not many rows!
t.String,
);
c.deepEqual(await executor([]), "returnedRow");
await c.throwsAsync(async () => await executor([]), {
instanceOf: errors.SQLQueryOutputValidationError,
message: "Expected array to contain exactly one element, but contained 2.",
});
await c.throwsAsync(async () => await executor([]), {
instanceOf: errors.SQLQueryOutputValidationError,
message: "Expected string[], but was string",
});
});
67 changes: 41 additions & 26 deletions typed-sql/src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,6 @@ import * as t from "runtypes";
import type * as query from "./input";
import * as errors from "./errors";

/* eslint-disable @typescript-eslint/ban-types */

// Unfortunately, runtypes is not capable of transforming values.
// There is some discussion in issue https://github.com/pelotom/runtypes/issues/56
// And PR: https://github.com/pelotom/runtypes/pull/191
// However, this doesn't seem to be happening anytime soon, so just leave this out for now.
// /**
// * Creates `runtypes` validator which ensures that input array of rows contains exactly one row with given shape.
// * @param singleRow The `runtypes` validator for row object.
// * @returns The `runtypes` validator which takes an array of rows as input, and ensures that array contains exactly one element. That element is then validated using given validator.
// */
// export const one = <TValidation extends t.Runtype>(
// singleRow: TValidation,
// ): t.Runtype<t.Static<TValidation>> =>
// many(singleRow).withConstraint<t.Static<TValidation>, void>(
// (array) =>
// // eslint-disable-next-line @typescript-eslint/no-unsafe-return
// array.length == 1
// ? array[0]
// : `Expected exactly 1 row, but got ${array.length}.`,
// { name: "SingleRow" },
// );

/**
* Creates `runtypes` validator which ensures that all rows of input array match given shape.
* @param singleRow The `runtypes` validator for row object.
Expand All @@ -39,9 +16,9 @@ export const many = <TValidation extends t.Runtype>(
): t.Runtype<Array<t.Static<TValidation>>> => t.Array(singleRow);

/**
* Creates {@link input.SQLQueryExecutor}, which takes input from rows produced from another {@link input.SQLQueryExecutor} and validate them using the given `runtypes` validation.
* Creates {@link input.SQLQueryExecutor}, which takes input from rows produced from another {@link input.SQLQueryExecutor} and validates them using the given `runtypes` validation.
* @param executor The {@link query.SQLQueryExecutor} which will provide the rows to validate.
* @param validation The `runtypes` validation, typically obtained via {@link one} or {@link many}.
* @param validation The `runtypes` validation, typically obtained via {@link many}.
* @returns The {@link input.SQLQueryExecutor}, which takes input from rows produced from another {@link input.SQLQueryExecutor} and validate them using the given `runtypes` validation.
*/
export const validateRows = <
Expand All @@ -60,13 +37,51 @@ export const validateRows = <
if (!maybeResult.success) {
throw new errors.SQLQueryOutputValidationError(maybeResult);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return maybeResult.value;
}
retVal.sqlString = executor.sqlString;
return retVal;
};

/**
* Creates {@link input.SQLQueryExecutor}, which takes input from rows produced from another {@link input.SQLQueryExecutor} and validates that only one row is present, and that the row passes the given `runtypes` validation.
* @param executor The {@link query.SQLQueryExecutor} which will provide the rows to validate.
* @param validation The `runtypes` validation for a single row.
* @returns The {@link input.SQLQueryExecutor}, which takes input from rows produced from another {@link input.SQLQueryExecutor} and validate them using the given `runtypes` validation.
*/
export const validateOneRow = <
TClient,
TParameters,
TValidation extends t.Runtype,
>(
executor: query.SQLQueryExecutor<TClient, TParameters, Array<unknown>>,
validation: TValidation,
): query.SQLQueryExecutor<TClient, TParameters, t.Static<TValidation>> => {
const validateRows = many(validation);
async function retVal(
client: ClientOf<typeof executor>,
parameters: ParametersOf<typeof executor>,
) {
const maybeResult = validateRows.validate(
await executor(client, parameters),
);
if (!maybeResult.success) {
throw new errors.SQLQueryOutputValidationError(maybeResult);
}
const len = maybeResult.value.length;
if (len !== 1) {
throw new errors.SQLQueryOutputValidationError({
success: false,
code: "CONSTRAINT_FAILED",
message: `Expected array to contain exactly one element, but contained ${len}.`,
});
}
return maybeResult.value[0];
}
retVal.sqlString = executor.sqlString;
return retVal;
};

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */
type ParametersOf<TExecutor extends query.SQLQueryExecutor<any, any, any>> =
TExecutor extends query.SQLQueryExecutor<
Expand Down

0 comments on commit 6d2aad8

Please sign in to comment.