diff --git a/.env b/.env
new file mode 100644
index 0000000..c8a6d4a
--- /dev/null
+++ b/.env
@@ -0,0 +1,4 @@
+# This is for tests only
+API_KEY=123
+ADMIN_EMAIL=alemagio@github.com
+EMAIL_CONFIG_JSON={ "foo": 42 }
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c8d01a6
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,19 @@
+name: CI workflow
+on: [push, pull_request]
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ node-version: [10.x, 12.x, 14.x]
+ os: [ubuntu-latest, windows-latest]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Install Dependencies
+ run: npm install --ignore-scripts
+ - name: Test
+ run: npm run test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1e8a627
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+# mac files
+.DS_Store
+
+# vim swap files
+*.swp
+
+# marko generated files
+*.marko.js
+# yarn.lock
+
+# lock files
+yarn.lock
+package-lock.json
+
+# IDE/editors
+.vscode
+.idea
+
+# temporary/work/output folders
+# build/
+out/
+temp/
+tmp/
diff --git a/.taprc b/.taprc
new file mode 100644
index 0000000..1721b52
--- /dev/null
+++ b/.taprc
@@ -0,0 +1,4 @@
+esm: false
+ts: false
+jsx: false
+coverage: false
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f5762de
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Alessandro Magionami
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f2db772
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+# fastify-envalid
+
+[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) ![CI workflow](__MY_PLUGIN_URL__
+/workflows/CI%20workflow/badge.svg)
+
+Supports Fastify versions `3.x`
+
+## Install
+```
+npm i fastify-envalid
+```
+
+## Usage
+Require `fastify-envalid` and register.
+```js
+const fastify = require('fastify')()
+
+fastify.register(require('fastify-envalid'))
+
+fastify.listen(3000)
+```
+
+The plugin will create 3 decorators on your `fastify` instance:
+
+* `cleanEnv` - A function which is exactly `envalid` [cleanEnv](https://github.com/af/envalid/blob/master/README.md#envalidcleanenvenvironment-validators-options)
+* `validators` - An object that contains all the `envalid` [validators](https://github.com/af/envalid/blob/master/README.md#validator-types)
+* `makeValidator` - A function which is exactly `envalid` [makeValidators](https://github.com/af/envalid/blob/master/README.md#custom-validators)
+
+## Acknowledgements
+
+The code is a port for Fastify of [`envalid`](https://github.com/af/envalid).
+
+## License
+
+Licensed under [MIT](./LICENSE).
diff --git a/examples/basic/server.js b/examples/basic/server.js
new file mode 100644
index 0000000..14e2a2f
--- /dev/null
+++ b/examples/basic/server.js
@@ -0,0 +1,18 @@
+const fastify = require('fastify')
+const fastifyEnvalid = require('../..')
+
+const app = fastify({ logger: true })
+
+app.register(fastifyEnvalid)
+
+app.get('/', async (req, res) => {
+ const env = app.cleanEnv(process.env, {
+ API_KEY: app.validators.str(),
+ ADMIN_EMAIL: app.validators.email({ default: 'admin@example.com' }),
+ EMAIL_CONFIG_JSON: app.validators.json({ desc: 'Additional email parameters' })
+ })
+
+ return { API_KEY: env.API_KEY, ADMIN_EMAIL: env.ADMIN_EMAIL, EMAIL_CONFIG_JSON: env.EMAIL_CONFIG_JSON }
+})
+
+app.listen(3000)
diff --git a/examples/typescript/server.ts b/examples/typescript/server.ts
new file mode 100644
index 0000000..c21d5ea
--- /dev/null
+++ b/examples/typescript/server.ts
@@ -0,0 +1,18 @@
+import fastify from 'fastify'
+import fastifyEnvalid from '../..'
+
+const app = fastify({ logger: true })
+
+app.register(fastifyEnvalid)
+
+app.get('/', async (req, res) => {
+ const env = app.cleanEnv(process.env, {
+ API_KEY: app.validators.str(),
+ ADMIN_EMAIL: app.validators.email({ default: 'admin@example.com' }),
+ EMAIL_CONFIG_JSON: app.validators.json({ desc: 'Additional email parameters' })
+ })
+
+ return { API_KEY: env.API_KEY, ADMIN_EMAIL: env.ADMIN_EMAIL, EMAIL_CONFIG_JSON: env.EMAIL_CONFIG_JSON }
+})
+
+app.listen(3000)
diff --git a/examples/typescript/tsconfig.json b/examples/typescript/tsconfig.json
new file mode 100644
index 0000000..2be494f
--- /dev/null
+++ b/examples/typescript/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "es2017"
+ }
+}
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..308355f
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,48 @@
+import { FastifyPlugin } from 'fastify'
+import { Spec, ValidatorSpec, CleanOptions, StrictCleanOptions, CleanEnv } from 'envalid'
+
+declare module 'fastify' {
+ export interface FastifyInstance {
+ validators: Validators
+ cleanEnv: CleanEnvFunction
+ makeValidator: (
+ parser: (input: string) => T,
+ type?: string
+ ) => (spec?: Spec) => ValidatorSpec
+ }
+}
+
+export interface Validators {
+
+ bool: (spec?: Spec) => ValidatorSpec
+
+ num: (spec?: Spec) => ValidatorSpec
+
+ str: (spec?: Spec) => ValidatorSpec
+
+ json: (spec?: Spec) => ValidatorSpec
+
+ url: (spec?: Spec) => ValidatorSpec
+
+ email: (spec?: Spec) => ValidatorSpec
+
+ host: (spec?: Spec) => ValidatorSpec
+
+ port: (spec?: Spec) => ValidatorSpec
+
+}
+
+export declare type CleanEnvFunction = (
+ environment: unknown,
+ validators?: { [K in keyof T]: ValidatorSpec },
+ options?: CleanOptions
+) => Readonly & CleanEnv
+
+export declare type StrictCleanEnvFunction = (
+ environment: unknown,
+ validators?: { [K in keyof T]: ValidatorSpec },
+ options?: StrictCleanOptions
+) => Readonly & CleanEnv & { readonly [varName: string]: string | undefined }
+
+declare const fastifyEnvalid: FastifyPlugin
+export default fastifyEnvalid
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..0e74611
--- /dev/null
+++ b/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const fp = require('fastify-plugin')
+const {
+ str,
+ bool,
+ num,
+ email,
+ host,
+ url,
+ json,
+ cleanEnv,
+ makeValidator
+} = require('envalid')
+
+module.exports = fp(async function (fastify, opts) {
+ fastify.decorate('validators', {
+ str,
+ bool,
+ num,
+ email,
+ host,
+ url,
+ json
+ })
+ fastify.decorate('cleanEnv', cleanEnv)
+ fastify.decorate('makeValidator', makeValidator)
+}, { fastify: '3.x' })
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a2fc046
--- /dev/null
+++ b/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "fastify-envalid",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "directories": {
+ "test": "test"
+ },
+ "scripts": {
+ "test": "npm run lint && npm run unit && npm run test:typescript",
+ "lint": "standard && npm run lint:typescript",
+ "lint:typescript": "ts-standard",
+ "test:typescript": "tsd",
+ "unit": "tap test/**/*.test.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "MIT",
+ "types": "index.d.ts",
+ "dependencies": {
+ "envalid": "^6.0.2",
+ "fastify-plugin": "^3.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^14.0.18",
+ "fastify": "^3.8.0",
+ "fastify-tsconfig": "^1.0.0",
+ "standard": "^14.3.3",
+ "tap": "^14.0.0",
+ "ts-standard": "^9.0.0",
+ "tsd": "^0.13.1",
+ "typescript": "^4.0.2"
+ },
+ "tsd": {
+ "directory": "test"
+ },
+ "files": [
+ "index.d.ts"
+ ],
+ "ts-standard": {
+ "ignore": [
+ "examples"
+ ]
+ }
+}
diff --git a/test/index.test-d.ts b/test/index.test-d.ts
new file mode 100644
index 0000000..8be8261
--- /dev/null
+++ b/test/index.test-d.ts
@@ -0,0 +1,19 @@
+import fastify from 'fastify'
+import fastifyEnvalid, { Validators, CleanEnvFunction, StrictCleanEnvFunction } from '..'
+import { expectType } from 'tsd'
+import { Spec, ValidatorSpec } from 'envalid'
+
+let app
+try {
+ app = fastify()
+ await app.ready()
+ await app.register(fastifyEnvalid)
+ expectType(app.validators)
+ expectType(app.cleanEnv)
+ expectType<(
+ parser: (input: string) => T,
+ type?: string
+ ) => (spec?: Spec) => ValidatorSpec>(app.makeValidator)
+} catch (err) {
+ console.error(err)
+}
diff --git a/test/index.test.js b/test/index.test.js
new file mode 100644
index 0000000..026c5ab
--- /dev/null
+++ b/test/index.test.js
@@ -0,0 +1,47 @@
+const { test } = require('tap')
+const {
+ str,
+ bool,
+ num,
+ email,
+ host,
+ url,
+ json,
+ cleanEnv,
+ makeValidator
+} = require('envalid')
+
+test('should register the correct decorators', async t => {
+ t.plan(6)
+
+ const app = require('fastify')()
+
+ app.register(require('..'))
+
+ await app.ready()
+
+ t.true(app.hasDecorator('validators'))
+ t.deepEqual(app.validators, {
+ str,
+ bool,
+ num,
+ email,
+ host,
+ url,
+ json
+ })
+ t.true(app.hasDecorator('cleanEnv'))
+ t.deepEqual(app.cleanEnv, cleanEnv)
+ t.true(app.hasDecorator('makeValidator'))
+ t.deepEqual(app.makeValidator, makeValidator)
+})
+
+test('should produce the correct env', async t => {
+ t.plan(1)
+ const app = require('fastify')()
+
+ app.register(require('..'))
+
+ await app.ready()
+ t.deepEqual(app.cleanEnv(process.env), cleanEnv(process.env))
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..7ddf54e
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "fastify-tsconfig",
+ "compilerOptions": {
+ "noEmit": true,
+ "esModuleInterop": true
+ },
+ "include": [
+ "**/*.ts"
+ ]
+}