Kwalify User's Guide (for Ruby)
release: $Release: $3 How to in Ruby
This section describes how to use Kwalify in Ruby.
3-1 Validation
require 'kwalify'
#require 'yaml'
## load schema data
schema = Kwalify::Yaml.load_file('schema.yaml')
## or
#schema = YAML.load_file('schema.yaml')
## create validator
validator = Kwalify::Validator.new(schema)
## load document
document = Kwalify::Yaml.load_file('document.yaml')
## or
#document = YAML.load_file('document.yaml')
## validate
errors = validator.validate(document)
## show errors
if errors && !errors.empty?
for e in errors
puts "[#{e.path}] #{e.message}"
end
end
3-2 Parsing with Validation
From version 0.7, Kwalify supports parsing with validation.
require 'kwalify'
#require 'yaml'
## load schema data
schema = Kwalify::Yaml.load_file('schema.yaml')
## or
#schema = YAML.load_file('schema.yaml')
## create validator
validator = Kwalify::Validator.new(schema)
## create parser with validator
## (if validator is ommitted, no validation executed.)
parser = Kwalify:::Yaml::Parser.new(validator)
## parse document with validation
filename = 'document.yaml'
document = parser.parse_file(filename)
## or
#document = parser.parse(File.read(filename), filename)
## show errors if exist
errors = parser.errors()
if errors && !errors.empty?
for e in errors
puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
end
end
3-3 Meta Validation
Meta validator is a validator which validates schema definition. The schema definition is placed at 'kwalify/kwalify.schema.yaml'.
Kwalify also provides Kwalify::MetaValidator class which validates schema defition.
require 'kwalify'
## meta validator
metavalidator = Kwalify::MetaValidator.instance
## validate schema definition
parser = Kwalify::Yaml::Parser.new(metavalidator)
errors = parser.parse_file('schema.yaml')
for e in errors
puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
end if errors && !errors.empty?
Meta validation is also available with command-line option '-m'.
$ kwalify -m schema1.yaml schema2.yaml ...
3-4 Validator#validator_hook()
You can extend Kwalify::Validator and override Kwalify::Validator#validator_hook() method. This method is called by Kwalify::Validator#validate().
type: map
mapping:
"answers":
type: seq
sequence:
- type: map
name: Answer
mapping:
"name": { type: str, required: yes }
"answer": { type: str, required: yes,
enum: [good, not bad, bad] }
"reason": { type: str }
#!/usr/bin/env ruby
require 'kwalify'
## validator class for answers
class AnswersValidator < Kwalify::Validator
## load schema definition
@@schema = Kwalify::Yaml.load_file('answers-schema.yaml')
## or
## require 'yaml'
## @@schema = YAML.load_file('answers-schema.yaml')
def initialize()
super(@@schema)
end
## hook method called by Validator#validate()
def validate_hook(value, rule, path, errors)
case rule.name
when 'Answer'
if value['answer'] == 'bad'
reason = value['reason']
if !reason || reason.empty?
msg = "reason is required when answer is 'bad'."
errors << Kwalify::ValidationError.new(msg, path)
end
end
end
end
end
## create validator
validator = AnswersValidator.new
## parse and validate YAML document
input = ARGF.read()
parser = Kwalify::Yaml::Parser.new(validator)
document = parser.parse(input)
## show errors
errors = parser.errors()
if !errors || errors.empty?
puts "Valid."
else
puts "*** INVALID!"
for e in errors
# e.class == Kwalify::ValidationError
puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
end
end
document07a.yaml : valid document exampleanswers:
- name: Foo
answer: good
reason: I like this style.
- name: Bar
answer: not bad
- name: Baz
answer: bad
reason: I don't like this style.
$ ruby answers-validator.rb document07a.yaml Valid.
document07b.yaml : invalid document exampleanswers:
- name: Foo
answer: good
- name: Bar
answer: bad
- name: Baz
answer: not bad
$ ruby answers-validator.rb document07b.yaml *** INVALID! 4:3 [/answers/1] reason is required when answer is 'bad'.
You can validate some document by a Validator instance because Validator class and Validator#validate() method are stateless. If you use instance variables in custom validator_hook() method, it becomes to be stateful.
3-5 Preceding Alias
From version 0.7, Kwalify allows aliases to appear before corresponding anchors are now appeared. These aliases are called as 'preceding alias'.
- name: Foo parent: *bar # preceding alias - &bar name: Bar parent: *baz # preceding alias - &baz name: Baz parent: null
To enable preceding alias, set Kwalify::Yaml::Parser#preceding_alias to true.
require 'kwalify'
parser = Kwalify::Yaml::Parser.new
parser.preceding_alias = true # enable preceding alias
ydoc = parser.parse_file('howto3.yaml')
require 'pp'
pp ydoc
$ ruby howto3.rb
[{"name"=>"Foo",
"parent"=>{"name"=>"Bar", "parent"=>{"name"=>"Baz", "parent"=>nil}}},
{"name"=>"Bar", "parent"=>{"name"=>"Baz", "parent"=>nil}},
{"name"=>"Baz", "parent"=>nil}]
Command-line option '-P' also enables preceding alias.
Preceding alias is very useful when document is complex.
3-6 Data Binding
From version 0.7, Kwalify supports data binding. * To enable data binding, set Kwlaify::Yaml::Parser#data_binding to true. * It is required to specify class name in schema definition. (Notice that 'class:' constraint is avaialbe only with rule which type is 'map'.) * Also instance methods '[]', '[]=', and 'keys?' must be defined in the classes. (Including Kwalify::Util::HashLike modules is easy way to define them.)
type: map
class: Config
mapping:
"host": { type: str, required: true }
"port": { type: int }
"user": { type: str, required: true }
"pass": { type: str, required: true }
host: localhost port: 8080 user: user1 pass: password1
## class definition
require 'kwalify/util/hashlike'
class Config
include Kwalify::Util::HashLike # defines [], []=, and keys?
attr_accessor :host, :posrt, :user, :pass
end
## create validator object
require 'kwalify'
schema = Kwalify::Yaml.load_file('config.schema.yaml')
validator = Kwalify::Validator.new(schema)
## parse configuration file with data binding
parser = Kwalify::Yaml::Parser.new(validator)
parser.data_binding = true # enable data binding
config = parser.parse_file('config.yaml')
require 'pp'
pp config
$ ruby loadconfig.rb #<Config: @host="localhost", @pass="password1", @port=8080, @user="user1">
Data binding is available even when data is more complex. Preceding alias is also available.
For example, the following data is complex because it uses anchor and alias (including preceding alias).
teams:
- &thechildren
name: The Children
desc: Level 7 ESPers
chief: *minamoto # preceding alias
members: [*kaoru, *aoi, *shiho] # preceding aliases
members:
- &minamoto
name: Kohichi Minamoto
desc: Scientist
team: *thechildren
- &kaoru
name: Kaoru Akashi
desc: Psychokino
team: *thechildren
- &aoi
name: Aoi Nogami
desc: Teleporter
team: *thechildren
- &shiho
name: Shiho Sannomiya
desc: Psycometrer
team: *thechildren
Here is the schema definition. (Notice that 'class:' constraint is avaialbe only with rule which type is 'map'.)
type: map
required: yes
mapping:
"teams":
type: seq
required: yes
sequence:
- &team
type: map
required: yes
class: Team
mapping:
"name": {type: str, required: yes, unique: yes}
"desc": {type: str}
"chief": *member # preceding alias
"members":
type: seq
sequence: [*member] # preceding alias
"members":
type: seq
required: yes
sequence:
- &member
type: map
required: yes
class: Member
mapping:
"name": {type: str, required: yes, unique: yes}
"desc": {type: str}
"team": *team
It is required to define class 'Team' and 'Member' for data-binding. Command-line option '-a genclass-ruby' will help you to generate class definitions from schema definition. Try 'kwalify -ha genclass-ruby' for more details about 'genclass-ruby' action.
$ kwalify -a genclass-ruby -P -f BABEL.schema.yaml \
--hashlike --initialize=false --module=Babel
require 'kwalify/util/hashlike'
module Babel
##
class Team
include Kwalify::Util::HashLike
attr_accessor :name # str
attr_accessor :desc # str
attr_accessor :chief # map
attr_accessor :members # seq
end
##
class Member
include Kwalify::Util::HashLike
attr_accessor :name # str
attr_accessor :desc # str
attr_accessor :team # map
end
end
$ kwalify -a genclass-ruby -P -f BABEL.schema.yaml \
--hashlike --initialize=false --module=Babel > models.rb
Here is the ruby program.
require 'kwalify'
require 'models'
## load schema definition
schema = Kwalify::Yaml.load_file('BABEL.schema.yaml',
:untabify=>true,
:preceding_alias=>true)
## add module name to 'class:'
Kwalify::Util.traverse_schema(schema) do |rulehash|
if rulehash['class']
rulehash['class'] = 'Babel::' + rulehash['class']
end
end
## create validator
validator = Kwalify::Validator.new(schema)
## parse with data-binding
parser = Kwalify::Yaml::Parser.new(validator)
parser.preceding_alias = true
parser.data_binding = true
ydoc = parser.parse_file('BABEL.data.yaml', :untabify=>true)
## show document
require 'pp'
pp ydoc
$ ruby loadbabel.rb
{"teams"=>
[#<Babel::Team:0x53e0f8
@chief=
#<Babel::Member:0x53d5e0
@desc="Scientist",
@name="Kohichi Minamoto",
@team=#<Babel::Team:0x53e0f8 ...>>,
@desc="Level 7 ESPers",
@members=
[#<Babel::Member:0x53d018
@desc="Psychokino",
@name="Kaoru Akashi",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53ca50
@desc="Teleporter",
@name="Aoi Nogami",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53c488
@desc="Psycometrer",
@name="Shiho Sannomiya",
@team=#<Babel::Team:0x53e0f8 ...>>],
@name="The Children">],
"members"=>
[#<Babel::Member:0x53d5e0
@desc="Scientist",
@name="Kohichi Minamoto",
@team=
#<Babel::Team:0x53e0f8
@chief=#<Babel::Member:0x53d5e0 ...>,
@desc="Level 7 ESPers",
@members=
[#<Babel::Member:0x53d018
@desc="Psychokino",
@name="Kaoru Akashi",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53ca50
@desc="Teleporter",
@name="Aoi Nogami",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53c488
@desc="Psycometrer",
@name="Shiho Sannomiya",
@team=#<Babel::Team:0x53e0f8 ...>>],
@name="The Children">>,
#<Babel::Member:0x53d018
@desc="Psychokino",
@name="Kaoru Akashi",
@team=
#<Babel::Team:0x53e0f8
@chief=
#<Babel::Member:0x53d5e0
@desc="Scientist",
@name="Kohichi Minamoto",
@team=#<Babel::Team:0x53e0f8 ...>>,
@desc="Level 7 ESPers",
@members=
[#<Babel::Member:0x53d018 ...>,
#<Babel::Member:0x53ca50
@desc="Teleporter",
@name="Aoi Nogami",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53c488
@desc="Psycometrer",
@name="Shiho Sannomiya",
@team=#<Babel::Team:0x53e0f8 ...>>],
@name="The Children">>,
#<Babel::Member:0x53ca50
@desc="Teleporter",
@name="Aoi Nogami",
@team=
#<Babel::Team:0x53e0f8
@chief=
#<Babel::Member:0x53d5e0
@desc="Scientist",
@name="Kohichi Minamoto",
@team=#<Babel::Team:0x53e0f8 ...>>,
@desc="Level 7 ESPers",
@members=
[#<Babel::Member:0x53d018
@desc="Psychokino",
@name="Kaoru Akashi",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53ca50 ...>,
#<Babel::Member:0x53c488
@desc="Psycometrer",
@name="Shiho Sannomiya",
@team=#<Babel::Team:0x53e0f8 ...>>],
@name="The Children">>,
#<Babel::Member:0x53c488
@desc="Psycometrer",
@name="Shiho Sannomiya",
@team=
#<Babel::Team:0x53e0f8
@chief=
#<Babel::Member:0x53d5e0
@desc="Scientist",
@name="Kohichi Minamoto",
@team=#<Babel::Team:0x53e0f8 ...>>,
@desc="Level 7 ESPers",
@members=
[#<Babel::Member:0x53d018
@desc="Psychokino",
@name="Kaoru Akashi",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53ca50
@desc="Teleporter",
@name="Aoi Nogami",
@team=#<Babel::Team:0x53e0f8 ...>>,
#<Babel::Member:0x53c488 ...>],
@name="The Children">>]}