Skip to content
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
6 changes: 6 additions & 0 deletions dataconnect-sdk/js/default-connector/index.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ exports.createMovieRef = function createMovieRef(dcOrVars, vars) {
exports.createMovie = function createMovie(dcOrVars, vars) {
return executeMutation(createMovieRef(dcOrVars, vars));
};

exports.upsertMovieRef = function upsertMovieRef(dcOrVars, vars) {
const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true);
dcInstance._useGeneratedSdk();
Expand All @@ -23,6 +24,7 @@ exports.upsertMovieRef = function upsertMovieRef(dcOrVars, vars) {
exports.upsertMovie = function upsertMovie(dcOrVars, vars) {
return executeMutation(upsertMovieRef(dcOrVars, vars));
};

exports.deleteMovieRef = function deleteMovieRef(dcOrVars, vars) {
const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true);
dcInstance._useGeneratedSdk();
Expand All @@ -31,6 +33,7 @@ exports.deleteMovieRef = function deleteMovieRef(dcOrVars, vars) {
exports.deleteMovie = function deleteMovie(dcOrVars, vars) {
return executeMutation(deleteMovieRef(dcOrVars, vars));
};

exports.addMetaRef = function addMetaRef(dc) {
const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined);
dcInstance._useGeneratedSdk();
Expand All @@ -39,6 +42,7 @@ exports.addMetaRef = function addMetaRef(dc) {
exports.addMeta = function addMeta(dc) {
return executeMutation(addMetaRef(dc));
};

exports.deleteMetaRef = function deleteMetaRef(dcOrVars, vars) {
const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true);
dcInstance._useGeneratedSdk();
Expand All @@ -47,6 +51,7 @@ exports.deleteMetaRef = function deleteMetaRef(dcOrVars, vars) {
exports.deleteMeta = function deleteMeta(dcOrVars, vars) {
return executeMutation(deleteMetaRef(dcOrVars, vars));
};

exports.listMoviesRef = function listMoviesRef(dc) {
const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined);
dcInstance._useGeneratedSdk();
Expand All @@ -55,6 +60,7 @@ exports.listMoviesRef = function listMoviesRef(dc) {
exports.listMovies = function listMovies(dc) {
return executeQuery(listMoviesRef(dc));
};

exports.getMovieByIdRef = function getMovieByIdRef(dcOrVars, vars) {
const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true);
dcInstance._useGeneratedSdk();
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/data-connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export {
useDataConnectMutation,
type useDataConnectMutationOptions,
} from "./useDataConnectMutation";
export type { QueryResultRequiredRef, UseDataConnectMutationResult, UseDataConnectQueryResult } from "./types";
export { validateReactArgs } from "./validateReactArgs";
export type { QueryResultRequiredRef, UseDataConnectMutationResult, UseDataConnectQueryResult } from "./types";
256 changes: 256 additions & 0 deletions packages/react/src/data-connect/validateReactArgs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import { describe, expect, test } from "vitest";
import { validateReactArgs } from "./validateReactArgs";
import { connectorConfig } from "@/dataconnect/default-connector";
import { getDataConnect } from "firebase/data-connect";
import { firebaseApp } from "~/testing-utils";

// initialize firebase app
firebaseApp;

describe("validateReactArgs", () => {
const dataConnect = getDataConnect(connectorConfig);

const emptyObjectVars = {};
const nonEmptyVars = { limit: 5 };

const options = { meta: { hasOptions: true } };

test.each([
{
argsDescription: "no args are provided",
dcOrOptions: undefined,
options: undefined,
expectedInputVars: undefined,
expectedInputOpts: undefined,
},
{
argsDescription: "only dataconnect is provided",
dcOrOptions: dataConnect,
options: undefined,
expectedInputVars: undefined,
expectedInputOpts: undefined,
},
{
argsDescription: "only options are provided",
dcOrOptions: options,
options: undefined,
expectedInputVars: undefined,
expectedInputOpts: options,
},
{
argsDescription: "dataconnect and options are provided",
dcOrOptions: dataConnect,
options: options,
expectedInputVars: undefined,
expectedInputOpts: options,
},
])(
"parses args correctly when $argsDescription for an operation with no variables",
({ dcOrOptions, options, expectedInputVars, expectedInputOpts }) => {
const {
dc: dcInstance,
vars: inputVars,
options: inputOpts,
} = validateReactArgs(
connectorConfig,
dcOrOptions,
options
// hasVars = undefined (false-y)
// validateArgs = undefined (false-y)
);

expect(dcInstance).toBe(dataConnect);
expect(inputVars).toBe(expectedInputVars);
expect(inputOpts).toBe(expectedInputOpts);
}
);

test.each([
{
argsDescription: "no args are provided",
dcOrVarsOrOptions: undefined,
varsOrOptions: undefined,
options: undefined,
expectedInputVars: undefined,
expectedInputOpts: undefined,
},
{
argsDescription: "only dataconnect is provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: undefined,
options: undefined,
expectedInputVars: undefined,
expectedInputOpts: undefined,
},
{
argsDescription: "only an empty vars object is provided",
dcOrVarsOrOptions: emptyObjectVars,
varsOrOptions: undefined,
options: undefined,
expectedInputVars: emptyObjectVars,
expectedInputOpts: undefined,
},
{
argsDescription: "only vars are provided",
dcOrVarsOrOptions: nonEmptyVars,
varsOrOptions: undefined,
options: undefined,
expectedInputVars: nonEmptyVars,
expectedInputOpts: undefined,
},
{
argsDescription: "only options are provided",
dcOrVarsOrOptions: undefined,
varsOrOptions: options,
options: undefined,
expectedInputVars: undefined,
expectedInputOpts: options,
},
{
argsDescription: "dataconnect and vars are provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: nonEmptyVars,
options: undefined,
expectedInputVars: nonEmptyVars,
expectedInputOpts: undefined,
},
{
argsDescription: "dataconnect and options are provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: undefined,
options: options,
expectedInputVars: undefined,
expectedInputOpts: options,
},
{
argsDescription: "dataconnect and vars and options are provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: nonEmptyVars,
options: options,
expectedInputVars: nonEmptyVars,
expectedInputOpts: options,
},
])(
"parses args correctly when $argsDescription for an operation with all optional variables",
({
dcOrVarsOrOptions,
varsOrOptions,
options,
expectedInputVars,
expectedInputOpts,
}) => {
const {
dc: dcInstance,
vars: inputVars,
options: inputOpts,
} = validateReactArgs(
connectorConfig,
dcOrVarsOrOptions,
varsOrOptions,
options,
true, // hasVars = true
false // validateArgs = false
);

expect(dcInstance).toBe(dataConnect);
expect(inputVars).toBe(expectedInputVars);
expect(inputOpts).toBe(expectedInputOpts);
}
);

test.each([
{
argsDescription: "only vars are provided",
dcOrVarsOrOptions: nonEmptyVars,
varsOrOptions: undefined,
options: undefined,
expectedInputVars: nonEmptyVars,
expectedInputOpts: undefined,
},
{
argsDescription: "dataconnect and vars are provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: nonEmptyVars,
options: undefined,
expectedInputVars: nonEmptyVars,
expectedInputOpts: undefined,
},
{
argsDescription: "vars and options are provided",
dcOrVarsOrOptions: nonEmptyVars,
varsOrOptions: options,
options: undefined,
expectedInputVars: nonEmptyVars,
expectedInputOpts: options,
},
{
argsDescription: "dataconnect and vars and options are provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: nonEmptyVars,
options: options,
expectedInputVars: nonEmptyVars,
expectedInputOpts: options,
},
])(
"parses args correctly when $argsDescription for an operation with any required variables",
({
dcOrVarsOrOptions,
varsOrOptions,
options,
expectedInputVars,
expectedInputOpts,
}) => {
const {
dc: dcInstance,
vars: inputVars,
options: inputOpts,
} = validateReactArgs(
connectorConfig,
dcOrVarsOrOptions,
varsOrOptions,
options,
true, // hasVars = true
true // validateArgs = true
);

expect(dcInstance).toBe(dataConnect);
expect(inputVars).toBe(expectedInputVars);
expect(inputOpts).toBe(expectedInputOpts);
}
);

test.each([
{
argsDescription: "only dataconnect is provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: undefined,
options: undefined,
},
{
argsDescription: "only options are provided",
dcOrVarsOrOptions: undefined,
varsOrOptions: options,
options: undefined,
},
{
argsDescription: "only dataconnect and options are provided",
dcOrVarsOrOptions: dataConnect,
varsOrOptions: undefined,
options: options,
},
])(
"throws error when $argsDescription for an operation with any required variables",
({ dcOrVarsOrOptions, varsOrOptions, options }) => {
expect(() => {
validateReactArgs(
connectorConfig,
dcOrVarsOrOptions,
varsOrOptions,
options,
true, // hasVars = true
true // validateArgs = true
);
}).toThrowError("invalid-argument: Variables required.");
}
);
});
68 changes: 68 additions & 0 deletions packages/react/src/data-connect/validateReactArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
ConnectorConfig,
DataConnect,
getDataConnect,
} from "firebase/data-connect";
import { useDataConnectQueryOptions } from "./useDataConnectQuery";

type DataConnectOptions =
| useDataConnectQueryOptions
| useDataConnectQueryOptions;

interface ParsedReactArgs<Variables> {
dc: DataConnect;
vars: Variables;
options: DataConnectOptions;
}

/**
* The generated React SDK will allow the user to pass in variables, a Data Connect instance, or operation options.
* The only required argument is the variables, which are only required when the operation has at least one required
* variable. Otherwise, all arguments are optional. This function validates the variables and returns back the DataConnect
* instance, variables, and options based on the arguments passed in.
* @param connectorConfig DataConnect connector config
* @param dcOrVarsOrOptions the first argument provided to a generated react function
* @param varsOrOptions the second argument provided to a generated react function
* @param options the third argument provided to a generated react function
* @param hasVars boolean parameter indicating whether the operation has variables
* @param validateVars boolean parameter indicating whether we should expect to find a value for realVars
* @returns parsed DataConnect, Variables, and Options for the operation
* @internal
*/
export function validateReactArgs<Variables extends object>(
connectorConfig: ConnectorConfig,
dcOrVarsOrOptions?: DataConnect | Variables | DataConnectOptions,
varsOrOptions?: Variables | DataConnectOptions,
options?: DataConnectOptions,
hasVars?: boolean,
Comment thread
stephenarosaj marked this conversation as resolved.
validateVars?: boolean
): ParsedReactArgs<Variables> {
let dcInstance: DataConnect;
let realVars: Variables;
let realOptions: DataConnectOptions;

if (dcOrVarsOrOptions && "enableEmulator" in dcOrVarsOrOptions) {
dcInstance = dcOrVarsOrOptions as DataConnect;
if (hasVars) {
realVars = varsOrOptions as Variables;
realOptions = options as DataConnectOptions;
} else {
realVars = undefined as unknown as Variables;
realOptions = varsOrOptions as DataConnectOptions;
}
} else {
dcInstance = getDataConnect(connectorConfig);
if (hasVars) {
realVars = dcOrVarsOrOptions as Variables;
realOptions = varsOrOptions as DataConnectOptions;
} else {
realVars = undefined as unknown as Variables;
realOptions = dcOrVarsOrOptions as DataConnectOptions;
}
}

if (!dcInstance || (!realVars && validateVars)) {
throw new Error("invalid-argument: Variables required."); // copied from firebase error codes
}
return { dc: dcInstance, vars: realVars, options: realOptions };
}