summaryrefslogtreecommitdiff
path: root/packages/astro/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro/src')
-rw-r--r--packages/astro/src/core/errors/errors-data.ts4
-rw-r--r--packages/astro/src/env/validators.ts151
-rw-r--r--packages/astro/src/env/vite-plugin-env.ts24
3 files changed, 105 insertions, 74 deletions
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index cae077053..236dd4dfd 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -1239,8 +1239,8 @@ export const RouteNotFound = {
export const EnvInvalidVariables = {
name: 'EnvInvalidVariables',
title: 'Invalid Environment Variables',
- message: (variables: string) =>
- `The following environment variables do not match the data type and/or properties defined in \`experimental.env.schema\`:\n\n${variables}\n`,
+ message: (errors: Array<string>) =>
+ `The following environment variables defined in \`experimental.env.schema\` are invalid:\n\n${errors.map(err => `- ${err}`).join('\n')}\n`,
} satisfies ErrorData;
/**
diff --git a/packages/astro/src/env/validators.ts b/packages/astro/src/env/validators.ts
index a09eeda8f..4e5d34287 100644
--- a/packages/astro/src/env/validators.ts
+++ b/packages/astro/src/env/validators.ts
@@ -1,16 +1,16 @@
import type { EnumSchema, EnvFieldType, NumberSchema, StringSchema } from './schema.js';
export type ValidationResultValue = EnvFieldType['default'];
+export type ValidationResultErrors = ['missing'] | ['type'] | Array<string>;
type ValidationResult =
| {
ok: true;
- type: string;
value: ValidationResultValue;
}
| {
ok: false;
- type: string;
+ errors: ValidationResultErrors;
};
export function getEnvFieldType(options: EnvFieldType) {
@@ -26,45 +26,50 @@ export function getEnvFieldType(options: EnvFieldType) {
return `${type}${optional ? ' | undefined' : ''}`;
}
-type ValueValidator = (input: string | undefined) => {
- valid: boolean;
- parsed: ValidationResultValue;
-};
+type ValueValidator = (input: string | undefined) => ValidationResult;
const stringValidator =
({ max, min, length, url, includes, startsWith, endsWith }: StringSchema): ValueValidator =>
(input) => {
- let valid = typeof input === 'string';
+ if (typeof input !== 'string') {
+ return {
+ ok: false,
+ errors: ['type'],
+ };
+ }
+ const errors: Array<string> = [];
- if (valid && max !== undefined) {
- valid = input!.length <= max;
+ if (max !== undefined && !(input.length <= max)) {
+ errors.push('max');
}
- if (valid && min !== undefined) {
- valid = input!.length >= min;
+ if (min !== undefined && !(input.length >= min)) {
+ errors.push('min');
}
- if (valid && length !== undefined) {
- valid = input!.length === length;
+ if (length !== undefined && !(input.length === length)) {
+ errors.push('length');
}
- if (valid && url !== undefined) {
- try {
- new URL(input!);
- } catch (_) {
- valid = false;
- }
+ if (url !== undefined && !URL.canParse(input)) {
+ errors.push('url');
}
- if (valid && includes !== undefined) {
- valid = input!.includes(includes);
+ if (includes !== undefined && !input.includes(includes)) {
+ errors.push('includes');
}
- if (valid && startsWith !== undefined) {
- valid = input!.startsWith(startsWith);
+ if (startsWith !== undefined && !input.startsWith(startsWith)) {
+ errors.push('startsWith');
}
- if (valid && endsWith !== undefined) {
- valid = input!.endsWith(endsWith);
+ if (endsWith !== undefined && !input.endsWith(endsWith)) {
+ errors.push('endsWith');
}
+ if (errors.length > 0) {
+ return {
+ ok: false,
+ errors,
+ };
+ }
return {
- valid,
- parsed: input,
+ ok: true,
+ value: input,
};
};
@@ -72,45 +77,71 @@ const numberValidator =
({ gt, min, lt, max, int }: NumberSchema): ValueValidator =>
(input) => {
const num = parseFloat(input ?? '');
- let valid = !isNaN(num);
+ if (isNaN(num)) {
+ return {
+ ok: false,
+ errors: ['type'],
+ };
+ }
+ const errors: Array<string> = [];
- if (valid && gt !== undefined) {
- valid = num > gt;
+ if (gt !== undefined && !(num > gt)) {
+ errors.push('gt');
}
- if (valid && min !== undefined) {
- valid = num >= min;
+ if (min !== undefined && !(num >= min)) {
+ errors.push('min');
}
- if (valid && lt !== undefined) {
- valid = num < lt;
+ if (lt !== undefined && !(num < lt)) {
+ errors.push('lt');
}
- if (valid && max !== undefined) {
- valid = num <= max;
+ if (max !== undefined && !(num <= max)) {
+ errors.push('max');
}
- if (valid && int !== undefined) {
+ if (int !== undefined) {
const isInt = Number.isInteger(num);
- valid = int ? isInt : !isInt;
+ if (!(int ? isInt : !isInt)) {
+ errors.push('int');
+ }
}
+ if (errors.length > 0) {
+ return {
+ ok: false,
+ errors,
+ };
+ }
return {
- valid,
- parsed: num,
+ ok: true,
+ value: num,
};
};
const booleanValidator: ValueValidator = (input) => {
const bool = input === 'true' ? true : input === 'false' ? false : undefined;
+ if (typeof bool !== 'boolean') {
+ return {
+ ok: false,
+ errors: ['type'],
+ };
+ }
return {
- valid: typeof bool === 'boolean',
- parsed: bool,
+ ok: true,
+ value: bool,
};
};
const enumValidator =
({ values }: EnumSchema): ValueValidator =>
(input) => {
+ if (!(typeof input === 'string' ? values.includes(input) : false)) {
+ return {
+ ok: false,
+ errors: ['type'],
+ };
+ }
return {
- valid: typeof input === 'string' ? values.includes(input) : false,
- parsed: input,
+ ok: true,
+ value: input,
};
};
@@ -131,29 +162,19 @@ export function validateEnvVariable(
value: string | undefined,
options: EnvFieldType
): ValidationResult {
- const validator = selectValidator(options);
-
- const type = getEnvFieldType(options);
-
- if (options.optional || options.default !== undefined) {
- if (value === undefined) {
- return {
- ok: true,
- value: options.default,
- type,
- };
- }
- }
- const { valid, parsed } = validator(value);
- if (valid) {
+ const isOptional = options.optional || options.default !== undefined;
+ if (isOptional && value === undefined) {
return {
ok: true,
- value: parsed,
- type,
+ value: options.default,
};
}
- return {
- ok: false,
- type,
- };
+ if (!isOptional && value === undefined) {
+ return {
+ ok: false,
+ errors: ['missing'],
+ };
+ }
+
+ return selectValidator(options)(value);
}
diff --git a/packages/astro/src/env/vite-plugin-env.ts b/packages/astro/src/env/vite-plugin-env.ts
index 1bcb021e0..9eeb7dcd9 100644
--- a/packages/astro/src/env/vite-plugin-env.ts
+++ b/packages/astro/src/env/vite-plugin-env.ts
@@ -9,7 +9,7 @@ import {
VIRTUAL_MODULES_IDS_VALUES,
} from './constants.js';
import type { EnvSchema } from './schema.js';
-import { validateEnvVariable } from './validators.js';
+import { getEnvFieldType, validateEnvVariable, type ValidationResultErrors } from './validators.js';
// TODO: reminders for when astro:env comes out of experimental
// Types should always be generated (like in types/content.d.ts). That means the client module will be empty
@@ -105,7 +105,7 @@ function validatePublicVariables({
validateSecrets: boolean;
}) {
const valid: Array<{ key: string; value: any; type: string; context: 'server' | 'client' }> = [];
- const invalid: Array<{ key: string; type: string }> = [];
+ const invalid: Array<{ key: string; type: string; errors: ValidationResultErrors }> = [];
for (const [key, options] of Object.entries(schema)) {
const variable = loadedEnv[key] === '' ? undefined : loadedEnv[key];
@@ -115,20 +115,30 @@ function validatePublicVariables({
}
const result = validateEnvVariable(variable, options);
+ const type = getEnvFieldType(options);
if (!result.ok) {
- invalid.push({ key, type: result.type });
+ invalid.push({ key, type, errors: result.errors });
// We don't do anything with validated secrets so we don't store them
} else if (options.access === 'public') {
- valid.push({ key, value: result.value, type: result.type, context: options.context });
+ valid.push({ key, value: result.value, type, context: options.context });
}
}
if (invalid.length > 0) {
+ const _errors: Array<string> = [];
+ for (const { key, type, errors } of invalid) {
+ if (errors[0] === 'missing') {
+ _errors.push(`${key} is missing`);
+ } else if (errors[0] === 'type') {
+ _errors.push(`${key}'s type is invalid, expected: ${type}`);
+ } else {
+ // constraints
+ _errors.push(`The following constraints for ${key} are not met: ${errors.join(', ')}`);
+ }
+ }
throw new AstroError({
...AstroErrorData.EnvInvalidVariables,
- message: AstroErrorData.EnvInvalidVariables.message(
- invalid.map(({ key, type }) => `Variable ${key} is not of type: ${type}.`).join('\n')
- ),
+ message: AstroErrorData.EnvInvalidVariables.message(_errors),
});
}