Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support users embedding fql snippets in objects and arrays #272

Merged
merged 4 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading