frise 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +131 -0
- data/Rakefile +13 -0
- data/frise.gemspec +31 -0
- data/lib/frise/defaults_loader.rb +69 -0
- data/lib/frise/loader.rb +60 -0
- data/lib/frise/parser.rb +17 -0
- data/lib/frise/validator.rb +179 -0
- data/lib/frise/version.rb +3 -0
- data/lib/frise.rb +5 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 41db8000684dfb7ee23cc7f26a790a57bb38ad76
|
4
|
+
data.tar.gz: 782cf2ff2d3866a754950fd8edd3735a1bb1f425
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c37c19e1f9edbad815b30d52f867d4344fd3b3da8a376e30236814a47ddd579b889459b1a702190fe5d1c1d2b99afdc3abef685f54a062616ea632872640a5e2
|
7
|
+
data.tar.gz: c9c87e93dbd3b361a5632431ac8f83650749c7d1de090c3397299b403cda7e1a986767356d8795dc3cf037f9461227a8287dd3870df79a573031ee5928d31931
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2017 ShiftForward, S.A. [http://www.shiftforward.eu]
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
4
|
+
use this file except in compliance with the License. You may obtain a copy of
|
5
|
+
the License at
|
6
|
+
|
7
|
+
[http://www.apache.org/licenses/LICENSE-2.0]
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
11
|
+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
12
|
+
License for the specific language governing permissions and limitations under
|
13
|
+
the License.
|
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Frise
|
2
|
+
[](https://travis-ci.org/ShiftForward/frise)
|
3
|
+
[](https://coveralls.io/github/ShiftForward/frise?branch=master)
|
4
|
+
|
5
|
+
Frise is a library for loading configuration files as native Ruby structures. Besides reading and
|
6
|
+
parsing the files themselves, it also:
|
7
|
+
|
8
|
+
- Completes it with default values specified in another file or set of files;
|
9
|
+
- Interprets [Liquid](https://shopify.github.io/liquid) templates in configs and defaults;
|
10
|
+
- Validates the loaded config according to a schema file or set of files.
|
11
|
+
|
12
|
+
## Install
|
13
|
+
|
14
|
+
```
|
15
|
+
gem install frise
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
### Basic configs
|
21
|
+
|
22
|
+
The simplest example would be to load [a simple configuration](example/config.yml) from a file:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'frise'
|
26
|
+
require 'pp'
|
27
|
+
|
28
|
+
loader = Frise::Loader.new
|
29
|
+
loader.load('example/config.yml')
|
30
|
+
# => {"movies"=>
|
31
|
+
# [{"title"=>"The Shawshank Redemption",
|
32
|
+
# "year"=>1994,
|
33
|
+
# "categories"=>["Crime", "Drama"],
|
34
|
+
# "rating"=>9.3},
|
35
|
+
# {"title"=>"The Godfather",
|
36
|
+
# "year"=>1972,
|
37
|
+
# "director"=>"Francis Ford Coppola",
|
38
|
+
# "categories"=>["Crime", "Drama"],
|
39
|
+
# "rating"=>9.2}]}
|
40
|
+
```
|
41
|
+
|
42
|
+
Currently Frise only supports YAML files, but it may support JSON and other formats in the future.
|
43
|
+
|
44
|
+
### Default values
|
45
|
+
|
46
|
+
By defining directories where the files with default values can be found (in this example,
|
47
|
+
[example/_defaults](example/_defaults)), Frise can handle its application internally on load time:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
loader = Frise::Loader.new(defaults_load_paths: ['example/_defaults'])
|
51
|
+
loader.load('example/config.yml')
|
52
|
+
# => {"movies"=>
|
53
|
+
# [{"title"=>"The Shawshank Redemption",
|
54
|
+
# "year"=>1994,
|
55
|
+
# "categories"=>["Crime", "Drama"],
|
56
|
+
# "rating"=>9.3,
|
57
|
+
# "director"=>"N/A"},
|
58
|
+
# {"title"=>"The Godfather",
|
59
|
+
# "year"=>1972,
|
60
|
+
# "director"=>"Francis Ford Coppola",
|
61
|
+
# "categories"=>["Crime", "Drama"],
|
62
|
+
# "rating"=>9.2}],
|
63
|
+
# "ui"=>
|
64
|
+
# {"default_movie"=>"The Shawshank Redemption",
|
65
|
+
# "filter_from"=>1972,
|
66
|
+
# "filter_to"=>1994}}
|
67
|
+
```
|
68
|
+
|
69
|
+
Note that files with default values follow exactly the same structure of the config file itself.
|
70
|
+
Special values such as `$all` allow users to define default values for all elements of an object or
|
71
|
+
array. Liquid templates are also used to define some defaults as a function of other objects of the
|
72
|
+
config.
|
73
|
+
|
74
|
+
### Schemas
|
75
|
+
|
76
|
+
Additionally, configuration files can also be validated against a schema. By specifying
|
77
|
+
`schema_load_paths`, users can provide directories where schema files such as
|
78
|
+
[example/_schemas/config.yml](example/_schemas/config.yml) can be found:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
loader = Frise::Loader.new(
|
82
|
+
schema_load_paths: ['example/_schemas'],
|
83
|
+
defaults_load_paths: ['example/_defaults'])
|
84
|
+
|
85
|
+
loader.load('example/config.yml')
|
86
|
+
# {"movies"=>
|
87
|
+
# [{"title"=>"The Shawshank Redemption",
|
88
|
+
# "year"=>1994,
|
89
|
+
# "categories"=>["Crime", "Drama"],
|
90
|
+
# "rating"=>9.3,
|
91
|
+
# "director"=>"N/A"},
|
92
|
+
# {"title"=>"The Godfather",
|
93
|
+
# "year"=>1972,
|
94
|
+
# "director"=>"Francis Ford Coppola",
|
95
|
+
# "categories"=>["Crime", "Drama"],
|
96
|
+
# "rating"=>9.2}],
|
97
|
+
# "ui"=>
|
98
|
+
# {"default_movie"=>"The Shawshank Redemption",
|
99
|
+
# "filter_from"=>1972,
|
100
|
+
# "filter_to"=>1994}}
|
101
|
+
```
|
102
|
+
|
103
|
+
If this config is loaded without the defaults instead, there are now required values that are
|
104
|
+
missing and Frise by default prints a summary of the errors and terminates the program:
|
105
|
+
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
loader = Frise::Loader.new(schema_load_paths: ['example/_schemas'])
|
109
|
+
|
110
|
+
loader.load('example/config.yml')
|
111
|
+
# 2 config error(s) found:
|
112
|
+
# - At movies.0.director: missing required value
|
113
|
+
# - At ui: missing required value
|
114
|
+
```
|
115
|
+
|
116
|
+
Once more, the structure of the schema mimics the structure of the config itself, making it easy to
|
117
|
+
write schemas and to create a config scaffold from its schema later.
|
118
|
+
|
119
|
+
Users can check a whole range of properties in config values besides their type: optional values,
|
120
|
+
hashes with validated keys, hashes with unknown keys and even custom validations are also supported.
|
121
|
+
The [specification](spec/frise/validator_spec.rb) of the validator provides various examples of
|
122
|
+
schemas and describes the behavior of each value (more documentation will be written soon).
|
123
|
+
|
124
|
+
### Other features
|
125
|
+
|
126
|
+
Users can also define custom code to be run before defaults and schemas are applied and can even do
|
127
|
+
each of the steps separately. Additionally, defaults and schemas can be loaded at a specific path
|
128
|
+
inside an existing Ruby object. The [Loader](lib/frise/loader.rb) class provides high-level methods
|
129
|
+
to access those features, while lower-level functionality can be accessed through
|
130
|
+
[Parser](lib/frise/parser.rb), [DefaultsLoader](lib/frise/defaults_loader.rb) and
|
131
|
+
[Validator](lib/frise/validator.rb).
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
def load_if_available(req_path)
|
4
|
+
require req_path
|
5
|
+
yield
|
6
|
+
rescue LoadError
|
7
|
+
false # req not available
|
8
|
+
end
|
9
|
+
|
10
|
+
load_if_available('rspec/core/rake_task') { RSpec::Core::RakeTask.new(:spec) }
|
11
|
+
load_if_available('rubocop/rake_task') { RuboCop::RakeTask.new(:rubocop) }
|
12
|
+
|
13
|
+
task default: %i[rubocop spec]
|
data/frise.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'frise/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'frise'
|
9
|
+
spec.version = Frise::VERSION
|
10
|
+
spec.authors = ['ShiftForward']
|
11
|
+
spec.email = ['info@shiftforward.eu']
|
12
|
+
|
13
|
+
spec.summary = 'Ruby config library with schema validation, default values and templating'
|
14
|
+
spec.homepage = 'https://github.com/ShiftForward/frise'
|
15
|
+
spec.license = 'Apache-2.0'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features|example)/})
|
19
|
+
end
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
spec.required_ruby_version = '>= 2.1.0'
|
22
|
+
|
23
|
+
spec.add_dependency 'liquid', '~> 3.0'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
26
|
+
spec.add_development_dependency 'coveralls', '~> 0.8.21'
|
27
|
+
spec.add_development_dependency 'simplecov', '~> 0.14.1'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.4'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.49.1'
|
31
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'frise/parser'
|
2
|
+
|
3
|
+
module Frise
|
4
|
+
# Provides the logic for merging config defaults into pre-loaded configuration objects.
|
5
|
+
#
|
6
|
+
# The merge_defaults and merge_defaults_at entrypoint methods provide ways to read files with
|
7
|
+
# defaults and apply them to configuration objects.
|
8
|
+
module DefaultsLoader
|
9
|
+
class << self
|
10
|
+
def widened_class(obj)
|
11
|
+
class_name = obj.class.to_s
|
12
|
+
return 'Boolean' if %w[TrueClass FalseClass].include? class_name
|
13
|
+
return 'Integer' if %w[Fixnum Bignum].include? class_name
|
14
|
+
class_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def merge_defaults_obj(config, defaults)
|
18
|
+
if defaults.nil?
|
19
|
+
config
|
20
|
+
|
21
|
+
elsif config.nil?
|
22
|
+
if defaults.class != Hash then defaults
|
23
|
+
elsif defaults['$optional'] then nil
|
24
|
+
else merge_defaults_obj({}, defaults)
|
25
|
+
end
|
26
|
+
|
27
|
+
elsif defaults.class == Array && config.class == Array
|
28
|
+
defaults + config
|
29
|
+
|
30
|
+
elsif defaults.class == Hash && defaults['$all'] && config.class == Array
|
31
|
+
config.map { |elem| merge_defaults_obj(elem, defaults['$all']) }
|
32
|
+
|
33
|
+
elsif defaults.class == Hash && config.class == Hash
|
34
|
+
new_config = {}
|
35
|
+
(config.keys + defaults.keys).uniq.each do |key|
|
36
|
+
next if key.start_with?('$')
|
37
|
+
new_config[key] = config[key]
|
38
|
+
new_config[key] = merge_defaults_obj(new_config[key], defaults[key]) if defaults.key?(key)
|
39
|
+
new_config[key] = merge_defaults_obj(new_config[key], defaults['$all']) unless new_config[key].nil?
|
40
|
+
new_config.delete(key) if new_config[key].nil?
|
41
|
+
end
|
42
|
+
new_config
|
43
|
+
|
44
|
+
elsif widened_class(defaults) != widened_class(config)
|
45
|
+
raise "Cannot merge config #{config.inspect} (#{widened_class(config)}) " \
|
46
|
+
"with default #{defaults.inspect} (#{widened_class(defaults)})"
|
47
|
+
|
48
|
+
else
|
49
|
+
config
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge_defaults_obj_at(config, at_path, defaults)
|
54
|
+
at_path.reverse.each { |key| defaults = { key => defaults } }
|
55
|
+
merge_defaults_obj(config, defaults)
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge_defaults(config, defaults_file, symbol_table = config)
|
59
|
+
defaults = Parser.parse(defaults_file, symbol_table)
|
60
|
+
merge_defaults_obj(config, defaults)
|
61
|
+
end
|
62
|
+
|
63
|
+
def merge_defaults_at(config, at_path, defaults_file, symbol_table = config)
|
64
|
+
defaults = Parser.parse(defaults_file, symbol_table)
|
65
|
+
merge_defaults_obj_at(config, at_path, defaults)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/frise/loader.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'frise/defaults_loader'
|
2
|
+
require 'frise/parser'
|
3
|
+
require 'frise/validator'
|
4
|
+
|
5
|
+
module Frise
|
6
|
+
# The entrypoint for loading configs from files according to the conventions defined for Frise.
|
7
|
+
#
|
8
|
+
# The load method loads a configuration file, merges it with the applicable defaults and validates
|
9
|
+
# its schema. Other methods in Loader perform only parts of the process.
|
10
|
+
class Loader
|
11
|
+
def initialize(schema_load_paths: [], defaults_load_paths: [], pre_loaders: [], validators: nil)
|
12
|
+
@schema_load_paths = schema_load_paths
|
13
|
+
@defaults_load_paths = defaults_load_paths
|
14
|
+
@pre_loaders = pre_loaders
|
15
|
+
@validators = validators
|
16
|
+
end
|
17
|
+
|
18
|
+
def load(config_file, exit_on_fail = true, symbol_table = {})
|
19
|
+
config = Parser.parse(config_file, symbol_table)
|
20
|
+
config_name = File.basename(config_file)
|
21
|
+
|
22
|
+
@pre_loaders.each do |pre_loader|
|
23
|
+
config = pre_loader.call(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
config = merge_defaults(config, config_name, symbol_table)
|
27
|
+
validate(config, config_name, exit_on_fail)
|
28
|
+
end
|
29
|
+
|
30
|
+
def merge_defaults(config, defaults_name, symbol_table = {})
|
31
|
+
merge_defaults_at(config, [], defaults_name, symbol_table)
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge_defaults_at(config, at_path, defaults_name, symbol_table = {})
|
35
|
+
@defaults_load_paths.map do |defaults_dir|
|
36
|
+
defaults_file = File.join(defaults_dir, defaults_name)
|
37
|
+
config = DefaultsLoader.merge_defaults_at(
|
38
|
+
config, at_path, defaults_file, symbol_table.merge(config)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
config
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate(config, schema_name, exit_on_fail = true)
|
45
|
+
validate_at(config, [], schema_name, exit_on_fail)
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_at(config, at_path, schema_name, exit_on_fail = true)
|
49
|
+
@schema_load_paths.map do |schema_dir|
|
50
|
+
schema_file = File.join(schema_dir, schema_name)
|
51
|
+
errors = Validator.validate_at(config, at_path, schema_file,
|
52
|
+
validators: @validators,
|
53
|
+
print: exit_on_fail,
|
54
|
+
fatal: exit_on_fail)
|
55
|
+
return nil if errors.any?
|
56
|
+
end
|
57
|
+
config
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/frise/parser.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'liquid'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Frise
|
5
|
+
# Provides a static parse method for reading a config from a YAML file applying the required
|
6
|
+
# transformations.
|
7
|
+
module Parser
|
8
|
+
class << self
|
9
|
+
def parse(file, symbol_table = nil)
|
10
|
+
return {} unless File.file? file
|
11
|
+
content = File.open(file).read
|
12
|
+
content = Liquid::Template.parse(content).render symbol_table if symbol_table
|
13
|
+
YAML.safe_load(content, [], [], true) || {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'frise/parser'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
module Frise
|
7
|
+
# Checks if a pre-loaded config object conforms to a schema file.
|
8
|
+
#
|
9
|
+
# The validate and validate_at static methods read schema files and validates config objects
|
10
|
+
# against the parsed schema. They can optionally be initialized with a set of user-defined
|
11
|
+
# validators that can be used in the schema files for custom validations.
|
12
|
+
class Validator
|
13
|
+
attr_reader :errors
|
14
|
+
|
15
|
+
def initialize(root, validators = nil)
|
16
|
+
@root = root
|
17
|
+
@validators = validators
|
18
|
+
@errors = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def widened_class(obj)
|
22
|
+
class_name = obj.class.to_s
|
23
|
+
return 'Boolean' if %w[TrueClass FalseClass].include? class_name
|
24
|
+
return 'Integer' if %w[Fixnum Bignum].include? class_name
|
25
|
+
class_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_validation_error(path, msg)
|
29
|
+
logged_path = path.empty? ? '<root>' : path[1..-1]
|
30
|
+
@errors << "At #{logged_path}: #{msg}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_full_schema(schema)
|
34
|
+
case schema
|
35
|
+
when Hash then schema
|
36
|
+
when Symbol then { type: 'Object', validate: schema }
|
37
|
+
when Array then
|
38
|
+
if schema.size == 1
|
39
|
+
{ type: 'Array', all: schema[0] }
|
40
|
+
else
|
41
|
+
(raise "Invalid schema: #{schema.inspect}")
|
42
|
+
end
|
43
|
+
when String then
|
44
|
+
if schema.end_with?('?')
|
45
|
+
{ type: schema[0..-2], optional: true }
|
46
|
+
else
|
47
|
+
{ type: schema }
|
48
|
+
end
|
49
|
+
else raise "Invalid schema: #{schema.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_optional(full_schema, obj, path)
|
54
|
+
if obj.nil?
|
55
|
+
add_validation_error(path, 'missing required value') unless full_schema[:optional]
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_expected_types(full_schema)
|
62
|
+
type_key = full_schema.fetch(:type, 'Hash')
|
63
|
+
allowed_types = %w[Hash Array String Integer Float Object]
|
64
|
+
return [Object.const_get(type_key)] if allowed_types.include?(type_key)
|
65
|
+
return [TrueClass, FalseClass] if type_key == 'Boolean'
|
66
|
+
raise "Invalid expected type in schema: #{type_key}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_type(full_schema, obj, path)
|
70
|
+
expected_types = get_expected_types(full_schema)
|
71
|
+
unless expected_types.any? { |typ| obj.is_a?(typ) }
|
72
|
+
type_key = full_schema.fetch(:type, 'Hash')
|
73
|
+
add_validation_error(path, "expected #{type_key}, found #{widened_class(obj)}")
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate_custom(full_schema, obj, path)
|
80
|
+
if full_schema[:validate]
|
81
|
+
begin
|
82
|
+
@validators.method(full_schema[:validate]).call(@root, obj)
|
83
|
+
rescue StandardError => ex
|
84
|
+
add_validation_error(path, ex.message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_spec_keys(full_schema, obj, path, processed_keys)
|
91
|
+
full_schema.each do |spec_key, spec_value|
|
92
|
+
next if spec_key.is_a?(Symbol)
|
93
|
+
validate_object("#{path}.#{spec_key}", obj[spec_key], spec_value)
|
94
|
+
processed_keys << spec_key
|
95
|
+
end
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate_remaining_keys(full_schema, obj, path, processed_keys)
|
100
|
+
expected_types = get_expected_types(full_schema)
|
101
|
+
if expected_types.size == 1 && expected_types[0].ancestors.member?(Enumerable)
|
102
|
+
hash = obj.is_a?(Hash) ? obj : Hash[obj.map.with_index { |x, i| [i, x] }]
|
103
|
+
hash.each do |key, value|
|
104
|
+
if full_schema[:all_keys] && !key.is_a?(Symbol)
|
105
|
+
validate_object(path, key, full_schema[:all_keys])
|
106
|
+
end
|
107
|
+
|
108
|
+
next if processed_keys.member? key
|
109
|
+
if full_schema[:all]
|
110
|
+
validate_object("#{path}.#{key}", value, full_schema[:all])
|
111
|
+
elsif !full_schema[:allow_unknown_keys]
|
112
|
+
add_validation_error(path, "unknown key: #{key}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def validate_object(path, obj, schema)
|
120
|
+
full_schema = get_full_schema(schema)
|
121
|
+
|
122
|
+
return unless validate_optional(full_schema, obj, path)
|
123
|
+
return unless validate_type(full_schema, obj, path)
|
124
|
+
return unless validate_custom(full_schema, obj, path)
|
125
|
+
|
126
|
+
processed_keys = Set.new
|
127
|
+
return unless validate_spec_keys(full_schema, obj, path, processed_keys)
|
128
|
+
validate_remaining_keys(full_schema, obj, path, processed_keys)
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.parse_symbols(obj)
|
132
|
+
case obj
|
133
|
+
when Array then obj.map { |e| parse_symbols(e) }
|
134
|
+
when Hash then Hash[obj.map { |k, v| [parse_symbols(k), parse_symbols(v)] }]
|
135
|
+
when String then obj.start_with?('$') ? obj[1..-1].to_sym : obj
|
136
|
+
else obj
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.validate_obj(config, schema, options = {})
|
141
|
+
validator = Validator.new(config, options[:validators])
|
142
|
+
validator.validate_object('', config, schema)
|
143
|
+
|
144
|
+
if validator.errors.any?
|
145
|
+
if options[:print]
|
146
|
+
puts "#{validator.errors.length} config error(s) found:"
|
147
|
+
validator.errors.each do |error|
|
148
|
+
puts " - #{error}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
exit 1 if options[:fatal]
|
153
|
+
raise ValidationError.new(validator.errors), 'Invalid configuration' if options[:raise_error]
|
154
|
+
end
|
155
|
+
validator.errors
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.validate(config, schema_file, options = {})
|
159
|
+
schema = parse_symbols(Parser.parse(schema_file))
|
160
|
+
validate_obj(config, schema, options)
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.validate_at(config, at_path, schema_file, options = {})
|
164
|
+
schema = parse_symbols(Parser.parse(schema_file))
|
165
|
+
at_path.reverse.each { |key| schema = { key => schema, :allow_unknown_keys => true } }
|
166
|
+
validate_obj(config, schema, options)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# An error resulting of the validation of a config. The list of errors can be inspected using the
|
171
|
+
# errors method.
|
172
|
+
class ValidationError < StandardError
|
173
|
+
attr_reader :errors
|
174
|
+
|
175
|
+
def initialize(errors)
|
176
|
+
@errors = errors
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/frise.rb
ADDED
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frise
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ShiftForward
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: liquid
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coveralls
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.21
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.21
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.14.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.14.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.49.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.49.1
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- info@shiftforward.eu
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- frise.gemspec
|
127
|
+
- lib/frise.rb
|
128
|
+
- lib/frise/defaults_loader.rb
|
129
|
+
- lib/frise/loader.rb
|
130
|
+
- lib/frise/parser.rb
|
131
|
+
- lib/frise/validator.rb
|
132
|
+
- lib/frise/version.rb
|
133
|
+
homepage: https://github.com/ShiftForward/frise
|
134
|
+
licenses:
|
135
|
+
- Apache-2.0
|
136
|
+
metadata: {}
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.1.0
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 2.6.11
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: Ruby config library with schema validation, default values and templating
|
157
|
+
test_files: []
|