Skip to content

Instantly share code, notes, and snippets.

@v-kolesnikov
Forked from flash-gordon/env_validation.rb
Created September 18, 2016 10:16
Show Gist options
  • Save v-kolesnikov/2aec064ecf292cdf6faa93a4eaaf71c5 to your computer and use it in GitHub Desktop.
Save v-kolesnikov/2aec064ecf292cdf6faa93a4eaaf71c5 to your computer and use it in GitHub Desktop.
ENV validation POC
require 'dry-validation'
class EnvValidation
class ENV
def inspect
ivs = instance_variables.each { |v| "#{v}=#{instance_variable_get(v)}" }.join(', ')
"ENV[#{ivs}]"
end
def to_s
"ENV[#{instance_variables.length} values]"
end
def method_missing(name, *)
raise RuntimeError, "Unknown env variable: #{name}"
end
def merge(kv)
dup.tap do |copy|
copy.instance_exec do
kv.each do |k, v|
instance_variable_set(:"@#{k}", v)
end
end
end
end
end
class Result
attr_reader :env, :errors
def initialize(env, errors)
@env = env
@errors = errors
end
def success?
@errors.nil?
end
end
class Schema
attr_reader :keys, :schema, :defaults
def initialize(keys, schema, defaults)
@keys = keys
@schema = schema
@defaults = defaults
@struct = Class.new(ENV) do
attr_reader *keys
singleton_class.send(:define_method, :keys) { keys }
define_method(:initialize) do |values|
keys.each do |key|
instance_variable_set(:"@#{key}", values.fetch(key) {
raise "#{key.inspect} not found in #{values.inspect}"
})
end
end
end
end
def call(values)
result = @schema.call(values)
if result.success?
Result.new(@struct.new(**defaults, **result.to_h), nil)
else
Result.new(nil, result.messages(full: true).values.flatten(1))
end
end
end
class DSL
def call(&block)
dsl = self
@defaults = {}
@keys = []
schema = Dry::Validation.Form do
form = self
dsl.instance_exec do
@form = form
instance_exec(&block)
end
end
Schema.new(@keys, schema, @defaults)
end
def var(name, type: :str, required: true, **other)
@keys << name
type_predicate = :"#{type}?"
if other.key?(:default)
@defaults[name] = other[:default]
@form.optional(name).filled(type_predicate)
elsif required
@form.required(name).filled(type_predicate)
else
@defaults[name] = nil
@form.optional(name).filled(type_predicate)
end
end
end
class Env
def initialize(schema, value)
@schema = schema
@value = value
end
end
def initialize(&block)
@schema = DSL.new.call(&block)
end
def call(values)
@schema.call(values.to_h)
end
alias_method :[], :call
end
require_relative './env_validation'
schema = EnvValidation.new do
var :IMPRINT_DATABASE_URL, type: :str
var :IMPRINT_PRINT_TASK_RESULTS, type: :str, default: '/tmp/imprint/print_tasks'
var :IMPRINT_REDIS_PROVIDER, type: :str, default: 'redis://127.0.0.1:6379'
var :IMPRINT_SIDEKIQ_TIMEOUT, type: :int, default: 5
var :IMPRINT_PRINT_TASK_MAIL_FROM, type: :str, required: false
var :IMPRINT_PRINT_TASK_MAIL_TO, type: :str
var :IMPRINT_PRINT_RESULT_HTTP, type: :str, required: false
var :IMPRINT_CLEAN_TMP_TIMEOUT, type: :int, default: 1
var :IMPRINT_SMTP_ADDRESS, type: :str, required: false
var :IMPRINT_SMTP_PORT, type: :int, required: false
var :IMPRINT_SMTP_DOMAIN, type: :str, required: false
var :IMPRINT_SMTP_ADDRESS, type: :str, required: false
var :IMPRINT_SMTP_USERNAME, type: :str, required: false
var :IMPRINT_SMTP_PASSWORD, type: :str, required: false
var :WEB_SESSIONS_SECRET, type: :str
end
result = schema[ENV]
if result.success?
Imprint = Module.new
Imprint::Env = result.env
Imprint::Env.class.keys.each do |key|
ENV.delete(key.to_s)
end
else
raise RuntimeError, "Environment misconfigured: #{result.errors.join(', ')}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment