Skip to content

Commit

Permalink
Support users embedding fql snippets in objects and arrays (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptpaterson authored Jun 26, 2024
1 parent 2db31aa commit 759018d
Show file tree
Hide file tree
Showing 9 changed files with 746 additions and 310 deletions.
4 changes: 2 additions & 2 deletions __tests__/integration/query-limits.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Client, fql, Module } from "../../src";
import { getClient, getDefaultSecretAndEndpoint } from "../client";
import { Client, fql } from "../../src";
import { getClient } from "../client";

const rootClient = getClient();
const clients = new Array<Client>();
Expand Down
68 changes: 67 additions & 1 deletion __tests__/integration/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,73 @@ describe("query can encode / decode QueryValue correctly", () => {
if (e instanceof TypeError) {
expect(e.name).toBe("TypeError");
expect(e.message).toBe(
"Passing undefined as a QueryValue is not supported",
"Passing undefined as a QueryArgument is not supported",
);
}
}
});

it("symbol arguments throw a TypeError", async () => {
expect.assertions(2);
// whack in a symbol
// @ts-expect-error Type 'symbol' is not assignable to type 'QueryValue'
let symbolValue: QueryValue = Symbol("foo");
try {
await client.query(fql`{ foo: ${symbolValue} }`);
} catch (e) {
if (e instanceof TypeError) {
expect(e.name).toBe("TypeError");
expect(e.message).toBe(
"Passing symbol as a QueryArgument is not supported",
);
}
}
});

it("function arguments throw a TypeError", async () => {
expect.assertions(2);
// whack in a function
let fnValue: QueryValue = () => {};
try {
await client.query(fql`{ foo: ${fnValue} }`);
} catch (e) {
if (e instanceof TypeError) {
expect(e.name).toBe("TypeError");
expect(e.message).toBe(
"Passing function as a QueryArgument is not supported",
);
}
}
});

it("symbol arguments throw a TypeError in arguments", async () => {
expect.assertions(2);
// whack in a symbol
// @ts-expect-error Type 'symbol' is not assignable to type 'QueryValue'
let symbolValue: QueryValue = Symbol("foo");
try {
await client.query(fql`foo`, { arguments: { foo: symbolValue } });
} catch (e) {
if (e instanceof TypeError) {
expect(e.name).toBe("TypeError");
expect(e.message).toBe(
"Passing symbol as a QueryArgument is not supported",
);
}
}
});

it("function arguments throw a TypeError in arguments", async () => {
expect.assertions(2);
// whack in a function
let fnValue: QueryValue = () => {};
try {
await client.query(fql`foo`, { arguments: { foo: fnValue } });
} catch (e) {
if (e instanceof TypeError) {
expect(e.name).toBe("TypeError");
expect(e.message).toBe(
"Passing function as a QueryArgument is not supported",
);
}
}
Expand Down
58 changes: 58 additions & 0 deletions __tests__/integration/template-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,64 @@ describe("query using template format", () => {
expect(response.data).toBe(true);
});

it("succeeds with deep nested expressions - example 2", async () => {
const str = "foo";
const otherStr = "bar";
const num = 6;
const otherNum = 3;
const deepFirst = fql`(${str} + ${otherStr})`;
const deeperBuilder = fql`(${num} + 3)`;
const innerQuery = fql`(${deeperBuilder} + ${otherNum})`;
const queryBuilder = fql`${deepFirst}.length + ${innerQuery}`;
const response = await client.query(queryBuilder);
expect(response.data).toBe(18);
});

it("succeeds with expressions nested within objects", async () => {
const arg = {
a: fql`1`,
b: fql`2`,
};
const queryBuilder = fql`${arg}`;
const response = await client.query(queryBuilder);
expect(response.data).toStrictEqual({ a: 1, b: 2 });
});

it("succeeds with expressions nested within arrays", async () => {
const arg = [fql`1`, fql`2`];
const queryBuilder = fql`${arg}`;
const response = await client.query(queryBuilder);
expect(response.data).toEqual([1, 2]);
});

it("succeeds with expressions nested within arrays and objects combined", async () => {
const arg = [
[fql`1`],
{
a: fql`1`,
b: fql`2`,
},
];
const queryBuilder = fql`${arg}`;
const response = await client.query(queryBuilder);
expect(response.data).toEqual([[1], { a: 1, b: 2 }]);
});

it("succeeds with multiple layers of nesting of arrays and objects", async () => {
const other = { a: fql`3`, b: fql`4` };
const arg = [
[fql`1 + ${fql`2`}`],
{
a: fql`1`,
b: fql`2`,
c: other,
},
];
const queryBuilder = fql`${arg}`;
const response = await client.query(queryBuilder);
expect(response.data).toEqual([[3], { a: 1, b: 2, c: { a: 3, b: 4 } }]);
});

it("succeeds with FQL string interpolation", async () => {
const codeName = "Alice";
const queryBuilder = fql`
Expand Down
116 changes: 46 additions & 70 deletions __tests__/unit/query-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,108 +3,117 @@ import { fql } from "../../src";
describe("fql method producing Querys", () => {
it("parses with no variables", () => {
const queryBuilder = fql`'foo'.length`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({ fql: ["'foo'.length"] });
expect(queryRequest.arguments).toStrictEqual({});
const fragment = queryBuilder.encode();
expect(fragment).toEqual({ fql: ["'foo'.length"] });
});

it("parses with a string variable", () => {
const str = "foo";
const queryBuilder = fql`${str}.length`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [{ value: "foo" }, ".length"],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with a number variable", () => {
const num = 8;
const queryBuilder = fql`'foo'.length == ${num}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: ["'foo'.length == ", { value: { "@int": "8" } }],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with a boolean variable", () => {
const bool = true;
const queryBuilder = fql`val.enabled == ${bool}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: ["val.enabled == ", { value: true }],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with a null variable", () => {
const queryBuilder = fql`value: ${null}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: ["value: ", { value: null }],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with an object variable", () => {
const obj = { foo: "bar", bar: "baz" };
const queryBuilder = fql`value: ${obj}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
fql: ["value: ", { value: { bar: "baz", foo: "bar" } }],
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [
"value: ",
{ object: { bar: { value: "baz" }, foo: { value: "bar" } } },
],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with an object variable having a toQuery property", () => {
const obj = { foo: "bar", bar: "baz", toQuery: "hehe" };
const queryBuilder = fql`value: ${obj}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
fql: ["value: ", { value: { bar: "baz", foo: "bar", toQuery: "hehe" } }],
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [
"value: ",
{
object: {
bar: { value: "baz" },
foo: { value: "bar" },
toQuery: { value: "hehe" },
},
},
],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with an array variable", () => {
const arr = [1, 2, 3];
const queryBuilder = fql`value: ${arr}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [
"value: ",
{ value: [{ "@int": "1" }, { "@int": "2" }, { "@int": "3" }] },
{
array: [
{ value: { "@int": "1" } },
{ value: { "@int": "2" } },
{ value: { "@int": "3" } },
],
},
,
],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with multiple variables", () => {
const str = "bar";
const num = 17;
const queryBuilder = fql`${str}.length == ${num + 3}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [{ value: "bar" }, ".length == ", { value: { "@int": "20" } }],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses nested expressions", () => {
const str = "baz";
const num = 17;
const innerQuery = fql`Math.add(${num}, 3)`;
const queryBuilder = fql`${str}.length == ${innerQuery}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [
{ value: "baz" },
".length == ",
{ fql: ["Math.add(", { value: { "@int": "17" } }, ", 3)"] },
],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses deep nested expressions", () => {
Expand All @@ -116,8 +125,8 @@ describe("fql method producing Querys", () => {
const deeperBuilder = fql`Math.add(${num}, 3)`;
const innerQuery = fql`Math.add(${deeperBuilder}, ${otherNum})`;
const queryBuilder = fql`${deepFirst}.length == ${innerQuery}`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [
{ fql: ["(", { value: "baz" }, " + ", { value: "bar" }, ")"] },
".length == ",
Expand All @@ -132,38 +141,6 @@ describe("fql method producing Querys", () => {
},
],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("adds headers if passed in", () => {
const str = "baz";
const num = 17;
const innerQuery = fql`Math.add(${num}, 3)`;
const queryBuilder = fql`${str}.length == ${innerQuery}`;
const queryRequest = queryBuilder.toQuery({
linearized: true,
query_timeout_ms: 600,
max_contention_retries: 4,
query_tags: { a: "tag" },
traceparent: "00-750efa5fb6a131eb2cf4db39f28366cb-5669e71839eca76b-00",
typecheck: false,
});
expect(queryRequest).toMatchObject({
linearized: true,
query_timeout_ms: 600,
max_contention_retries: 4,
query_tags: { a: "tag" },
traceparent: "00-750efa5fb6a131eb2cf4db39f28366cb-5669e71839eca76b-00",
typecheck: false,
});
expect(queryRequest.query).toEqual({
fql: [
{ value: "baz" },
".length == ",
{ fql: ["Math.add(", { value: { "@int": "17" } }, ", 3)"] },
],
});
expect(queryRequest.arguments).toStrictEqual({});
});

it("parses with FQL string interpolation", async () => {
Expand All @@ -172,14 +149,13 @@ describe("fql method producing Querys", () => {
let name = ${codeName}
"Hello, #{name}"
`;
const queryRequest = queryBuilder.toQuery();
expect(queryRequest.query).toEqual({
const fragment = queryBuilder.encode();
expect(fragment).toEqual({
fql: [
"\n let name = ",
{ value: "Alice" },
'\n "Hello, #{name}"\n ',
],
});
expect(queryRequest.arguments).toStrictEqual({});
});
});
Loading

0 comments on commit 759018d

Please sign in to comment.