Home > database >  Can you infer the return type of a function from a parameter?
Can you infer the return type of a function from a parameter?

Time:10-06

A function getFormData() validates and sanitises a FormData object using some provided schema. If there are no validation errors, the function will return the sanitised FormData; otherwise the function will throw an error.

Problem:

The current implementation of getFormData() returns a FormDataValues type (i.e. Record<string, any>); and takes a parameter of type Schema (i.e. Record<string, ValidatorFunction<any>>) where any is the returned value type of the ValidatorFunction.

This current implementation is not type-safe. How can I instead ensure type-safety by inferring the return type of the getFormData() function from the provided schema?

Example:

The expected implementation could take a schema object of type { name: ValidatorFunction<string> } and return a values object of type { name: string }.

// api.ts
const validateName: ValidatorFunction<string> = (name) => {
  if (!name) {
    return { value: name, error: "Name is required" };
  }

  if (typeof name !== "string") {
    return { value: name, error: "Name must be a string" };
  }

  name = name.trim();

  return { value: name, error: null };
};

const schema = { name: validateName };

const action = async ({ request }) => {
  try {
    /**
     * Infer return type of getFormData() from schema parameter, e.g.
     * const schema: { name: ValidatorFunction<string> } ->
     * const values: { name: string }
     */
    const values = await getFormData(request, schema);

    return null;
  } catch (error) {
    console.error(error);
  }
};
// form-data.ts
type FormDataValue = FormDataEntryValue | FormDataEntryValue[] | null;
type ValidatorFunction<T> = (value: FormDataValue) => { value: T; error: null } | { value: FormDataValue; error: string };
type Schema = Record<string, ValidatorFunction<any>>;
type FormDataValues = Record<string, any>;
type FormDataErrors = Record<string, string>;

const getFormData = async (request: Request, schema: Schema) => {
  const formData = await request.formData();
  const formDataValues: FormDataValues = {};
  const formDataErrors: FormDataErrors = {};

  for (const field in schema) {
    const formDataValue = _parseFormDataValue(formData, field);
    const validator = schema[field];
    const { value, error } = validator(formDataValue);

    if (error) {
      formDataErrors[field] = error;
    }

    formDataValues[field] = value;
  }

  if (Object.keys(formDataErrors).length !== 0) {
    throw formDataErrors;
  }

  return formDataValues;
};

const _parseFormDataValue = (formData: FormData, field: string) => {
  const formDataEntryValues = formData.getAll(field);

  if (formDataEntryValues.length == 1) {
    return formDataEntryValues[0];
  }

  if (formDataEntryValues.length > 1) {
    return formDataEntryValues;
  }

  return null;
};

CodePudding user response:

First we can use a conditional type to infer the value type from a validator function.

type ValidatorFunctionReturn<F> = F extends ValidatorFunction<infer T> ? T : F;

type ValidateNameReturn = ValidatorFunctionType<typeof validateName> // string

The we can create a mapped type that maps over a Schema keys and uses the conditional type to infer the type for the key.

type SchemaValues<S extends Schema> = {
  [P in keyof S]: ValidatorFunctionReturn<S[P]>;
}

const schema = { name: validateName };

type Foo = SchemaValues<typeof schema> // {name: string}

Then your getFormData function would look something like this:

const getFormData = async <S extends Schema>(request: Request, schema: S) : Promise<SchemaValues<S>> => {
  // ...
}
  • Related