Kwalify Users' Guide (for Ruby and Java)
last update: $Date: 2006-05-14 13:57:07 +0900 (Sun, 14 May 2006) $
Preface
Kwalify(*1) is a tiny schema validator for YAML and JSON document.
You know "80-20 rule" known as Pareto Law, don't you? This rule suggests that 20% of the population owns 80% of the wealth. Kwalify is based on a new "50-5 rule" which suggests that 5% of the population owns 50% of the wealth. This rule is more aggressive and cost-effective than Pareto Law. The rule is named as "Levi's Law".
| schema technology | (A) cover range | (B) cost to pay | (A)/(B) effectiveness |
|---|---|---|---|
| XML Schema | 95% | 100% | 0.95 (= 95/100) |
| RelaxNG | 80% | 20% | 4.0 (= 80/20) |
| Kwalify | 50% | 5% | 10.0 (= 50/5) |
Kwalify is small and in fact poorer than RelaxNG or XML Schema. I hope you extend/customize Kwalify for your own way.
- (*1)
- Pronounce as 'Qualify'.
Table of Contents
Schema Definition
Sequence
schema01.yaml : sequence of stringtype: seq sequence: - type: str
document01a.yaml : valid document example- foo - bar - baz
$ kwalify -lf schema01.yaml document01a.yaml document01a.yaml#0: valid.
document01b.yaml : invalid document example- foo - 123 - baz
$ kwalify -lf schema01.yaml document01b.yaml document01b.yaml#0: INVALID - (line 2) [/1] '123': not a string.
Default 'type:' is str so you can omit 'type: str'.
Mapping
schema02.yaml : mapping of scalartype: map
mapping:
name:
type: str
required: yes
email:
type: str
pattern: /@/
age:
type: int
birth:
type: date
document02a.yaml : valid document examplename: foo email: foo@mail.com age: 20 birth: 1985-01-01
$ kwalify -lf schema02.yaml document02a.yaml document02a.yaml#0: valid.
document02b.yaml : invalid document examplename: foo email: foo(at)mail.com age: twenty birth: Jun 01, 1985
$ kwalify -lf schema02.yaml document02b.yaml document02b.yaml#0: INVALID - (line 2) [/email] 'foo(at)mail.com': not matched to pattern /@/. - (line 3) [/age] 'twenty': not a integer. - (line 4) [/birth] 'Jun 01, 1985': not a date.
Sequence of Mapping
schema03.yaml : sequence of mappingtype: seq
sequence:
- type: map
mapping:
name:
type: str
required: true
email:
type: str
document03a.yaml : valid document example- name: foo email: foo@mail.com - name: bar email: bar@mail.net - name: baz email: baz@mail.org
$ kwalify -lf schema03.yaml document03a.yaml document03a.yaml#0: valid.
document03b.yaml : invalid document example- name: foo email: foo@mail.com - naem: bar email: bar@mail.net - name: baz mail: baz@mail.org
$ kwalify -lf schema03.yaml document03b.yaml document03b.yaml#0: INVALID - (line 3) [/1] key 'name:' is required. - (line 3) [/1/naem] key 'naem:' is undefined. - (line 6) [/2/mail] key 'mail:' is undefined.
Mapping of Sequence
schema04.yaml : mapping of sequence of mappingtype: map
mapping:
company:
type: str
required: yes
email:
type: str
employees:
type: seq
sequence:
- type: map
mapping:
code:
type: int
required: yes
name:
type: str
required: yes
email:
type: str
document04a.yaml : valid document examplecompany: Kuwata lab.
email: webmaster@kuwata-lab.com
employees:
- code: 101
name: foo
email: foo@kuwata-lab.com
- code: 102
name: bar
email: bar@kuwata-lab.com
$ kwalify -lf schema04.yaml document04a.yaml document04a.yaml#0: valid.
document04b.yaml : invalid document examplecompany: Kuwata Lab.
email: webmaster@kuwata-lab.com
employees:
- code: A101
name: foo
email: foo@kuwata-lab.com
- code: 102
name: bar
mail: bar@kuwata-lab.com
$ kwalify -lf schema04.yaml document04b.yaml document04b.yaml#0: INVALID - (line 4) [/employees/0/code] 'A101': not a integer. - (line 9) [/employees/1/mail] key 'mail:' is undefined.
Rule and Entry
Rule is set of entries. Entry usually represents constraint outside of a few exceptions.
The followings are constraint entries.
-
required: - Value is required when true (default is false).
-
enum: - List of available values.
-
pattern: - Specifies regular expression pattern of value.
-
type: -
Type of value. The followings are available:
strintfloatnumber(== int or float)text(== str or number)booldatetimetimestampseqmapscalar(all but seq and map)any(means any data)
-
range: -
Range of value between max/max-ex and min/min-ex.
- 'max' means 'max-inclusive'.
- 'min' means 'min-inclusive'.
- 'max-ex' means 'max-exclusive'.
- 'min-ex' means 'min-exclusive'.
seq,map,boolandanyare not available withrange:. -
length: -
Range of length of value between max/max-ex and min/min-ex. Only type
strandtextare available withlength:. -
assert: -
String which represents validation expression. String should contain variable name
valwhich repsents value. (This is an experimental function and supported only Kwartz-ruby). -
unique: - Value is unique for mapping or sequence. See the next subsection for detail.
The followings are non-constraint entries.
-
name: - Name of schema.
-
desc: - Description. This is not used for validation.
-
classname: - Class name. This is available only with type 'map' and used with 'genclass' action. See 'Actions' section for details.
Rule contains 'type:' entry. 'sequence:' entry takes a list of rule. 'mapping:' entry takes a hash which values are rules.
schema05.yaml : rule examplestype: seq # new rule
sequence:
-
type: map # new rule
mapping:
name:
type: str # new rule
required: yes
email:
type: str # new rule
required: yes
pattern: /@/
password:
type: text # new rule
length: { max: 16, min: 8 }
age:
type: int # new rule
range: { max: 30, min: 18 }
# or assert: 18 <= val && val <= 30
blood:
type: str # new rule
enum:
- A
- B
- O
- AB
birth:
type: date # new rule
memo:
type: any # new rule
document05a.yaml : valid document example- name: foo email: foo@mail.com password: xxx123456 age: 20 blood: A birth: 1985-01-01 - name: bar email: bar@mail.net age: 25 blood: AB birth: 1980-01-01
$ kwalify -lf schema05.yaml document05a.yaml document05a.yaml#0: valid.
document05b.yaml : invalid document example- name: foo email: foo(at)mail.com password: xxx123 age: twenty blood: a birth: 1985-01-01 - given-name: bar family-name: Bar email: bar@mail.net age: 15 blood: AB birth: 1980/01/01
$ kwalify -lf schema05.yaml document05b.yaml document05b.yaml#0: INVALID - (line 2) [/0/email] 'foo(at)mail.com': not matched to pattern /@/. - (line 3) [/0/password] 'xxx123': too short (length 6 < min 8). - (line 4) [/0/age] 'twenty': not a integer. - (line 5) [/0/blood] 'a': invalid blood value. - (line 7) [/1/given-name] key 'given-name:' is undefined. - (line 7) [/1] key 'name:' is required. - (line 8) [/1/family-name] key 'family-name:' is undefined. - (line 10) [/1/age] '15': too small (< min 18). - (line 12) [/1/birth] '1980/01/01': not a date.
Unique constraint
'unique:' constraint entry is available with elements of sequence or mapping.
This is equivalent to unique key or primary key of RDBMS.
Type of rule which has 'unique:' entry must be scalar (str, int, float, ...).
Type of parent rule must be sequence or mapping.
schema06.yaml : unique constraint entry with mapping and sequencetype: seq
sequence:
- type: map
required: yes
mapping:
name:
type: str
required: yes
unique: yes
email:
type: str
groups:
type: seq
sequence:
- type: str
unique: yes
document06a.yaml : valid document example- name: foo
email: admin@mail.com
groups:
- users
- foo
- admin
- name: bar
email: admin@mail.com
groups:
- users
- admin
- name: baz
email: baz@mail.com
groups:
- users
$ kwalify -lf schema06.yaml document06a.yaml document06a.yaml#0: valid.
document06b.yaml : invalid document example- name: foo
email: admin@mail.com
groups:
- foo
- users
- admin
- foo
- name: bar
email: admin@mail.com
groups:
- admin
- users
- name: bar
email: baz@mail.com
groups:
- users
$ kwalify -lf schema06.yaml document06b.yaml document06b.yaml#0: INVALID - (line 7) [/0/groups/3] 'foo': is already used at '/0/groups/0'. - (line 13) [/2/name] 'bar': is already used at '/1/name'.
Validator#validator_hook()
You can extend Kwalify::Validator class (Ruby) or kwalify.Validator class (Java), and override Kwalify::Validator#validator_hook() method (Ruby) or kwalify.Validator#validateHook() method (Java). This method is called by Kwalify::Validator#validate() (Ruby) or kwalify.Validator#validate() (Java).
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'
require 'yaml'
## validator class for answers
class AnswersValidator < Kwalify::Validator
## load schema definition
@@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
## load YAML document
input = ARGF.read()
document = YAML.load(input)
## validate
errors = validator.validate(document)
if errors.empty?
puts "Valid."
else
puts "*** INVALID!"
errors.each do |error|
# error.class == Kwalify::ValidationError
puts " - [#{error.path}] : #{error.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! - [/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.
Here is a Java program equivarent to 'answers-validator.rb'.
import kwalify.Validator;
import kwalify.Rule;
import kwalify.Util;
import kwalify.YamlUtil;
import kwalify.YamlParser;
import kwalify.SyntaxException;
import kwalify.ValidationException;
import java.util.*;
import java.io.IOException;
/**
* validator class for answers
*/
public class AnswersValidator extends Validator {
/** schema string */
private static final String SCHEMA = ""
+ "type: map\n"
+ "mapping:\n"
+ " answers:\n"
+ " type: seq\n"
+ " sequence:\n"
+ " - type: map\n"
+ " name: Answer\n"
+ " mapping:\n"
+ " name:\n"
+ " type: str\n"
+ " required: yes\n"
+ " answer:\n"
+ " type: str\n"
+ " required: yes\n"
+ " enum:\n"
+ " - good\n"
+ " - not bad\n"
+ " - bad\n"
+ " reason:\n"
+ " type: str\n"
;
/** schema object */
private static Map schema = null;
static {
try {
schema = (Map)YamlUtil.load(SCHEMA);
} catch (SyntaxException ex) {
assert false;
}
}
/** construnctor */
public AnswersValidator() {
super(schema);
}
/** hook method called by Validator#validate() */
protected void validateHook(Object value, Rule rule, String path, List errors) {
String rule_name = rule.getName();
if (rule_name != null && rule_name.equals("Answer")) {
assert value instanceof Map;
Map val = (Map)value;
assert val.get("answer") != null;
if (val.get("answer").equals("bad")) {
String reason = (String)val.get("reason");
if (reason == null || reason.length() == 0) {
String msg = "reason is required when answer is 'bad'.";
errors.add(new ValidationException(msg, path));
}
}
}
}
/** main program */
public static void main(String[] args) throws IOException, SyntaxException {
// create validator
Validator validator = new AnswersValidator();
// load YAML document
String input;
if (args.length > 0) {
input = Util.readFile(args[0]);
} else {
input = Util.readInputStream(System.in);
}
YamlParser parser = new YamlParser(input);
Object document = parser.parse();
// validate and show errors
List errors = validator.validate(document);
if (errors == null || errors.size() == 0) {
System.out.println("Valid.");
} else {
System.out.println("*** INVALID!");
parser.setErrorsLineNumber(errors);
Collections.sort(errors);
for (Iterator it = errors.iterator(); it.hasNext(); ) {
ValidationException error = (ValidationException)it.next();
int linenum = error.getLineNumber();
String path = error.getPath();
String mesg = error.getMessage();
String s = "- line " + linenum + ": [" + path + "] " + mesg;
System.out.println(s);
}
}
}
}
$ java -classpath kwalify.jar AnswersValidator document07a.yaml Valid. $ java -classpath kwalify.jar AnswersValidator document07b.yaml *** INVALID! - line 4: [/answers/1] reason is required when answer is 'bad'.
Validator with Block
Notice: This feature has been obsolete. Use Kwalify::Validator#validate_hook() method instead.
Kwalify::Validator.new() method can take a block which is invoked when validation.
validate08.rb : validate script#!/usr/bin/env ruby
require 'kwalify'
require 'yaml'
## load schema definition
schema = YAML.load_file('answers-schema.yaml')
## create validator for answers
validator = Kwalify::Validator.new(schema) { |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
}
## load YAML document
input = ARGF.read()
document = YAML.load(input)
## validate
errors = validator.validate(document)
if errors.empty?
puts "Valid."
else
puts "*** INVALID!"
errors.each do |error|
# error.class == Kwalify::ValidationError
puts " - [#{error.path}] : #{error.message}"
end
end
$ ruby validate08.rb document07a.yaml Valid.
$ ruby validate08.rb document07b.yaml *** INVALID! - [/answers/1] : reason is required when answer is 'bad'.
Tips
Enclose Key Names in (Double) Quotes
It is allowed to enclose key name in quotes (') or double-quotes (") in YAML. This tip highlights user-defined key names.
schema11a.yaml : enclosing in double-quotestype: map
mapping:
"name":
required: yes
"email":
pattern: /@/
"age":
type: int
"birth":
type: date
You may prefer to indent with 1 space and 3 spaces.
schema11b.yaml : indent with 1 space and 3 spacestype: map
mapping:
"name":
required: yes
"email":
pattern: /@/
"age":
type: int
"birth":
type: date
JSON
JSON is a lightweight data-interchange format, especially useful for JavaScript. JSON can be considered as a subset of YAML. It means that YAML parser can parse JSON and Kwalify can validate JSON document.
schema12.yaml : an example schema written in JSON format{ "type": "map",
"required": true,
"mapping": {
"name": { "type": "str", "required": true },
"email": { "type": "str" },
"age": { "type": "int" },
"gender": { "type": "str", "enum": ["M", "F"] },
"favorite": { "type": "seq",
"sequence": [
{ "type": "str" }
]
}
}
}
document12a.yaml : valid JSON document example{ "name": "Foo",
"email": "foo@mail.com",
"age": 20,
"gender": "F",
"favorite": [
"football",
"basketball",
"baseball"
]
}
$ kwalify -lf schema12.yaml document12a.yaml document12a.yaml#0: valid.
document12b.yaml : invalid JSON document example{
"mail": "foo@mail.com",
"age": twenty,
"gender": "X",
"favorite": [ 123, 456 ]
}
$ kwalify -lf schema12.yaml document12b.yaml document12b.yaml#0: INVALID - (line 1) [/] key 'name:' is required. - (line 2) [/mail] key 'mail:' is undefined. - (line 3) [/age] 'twenty': not a integer. - (line 4) [/gender] 'X': invalid gender value. - (line 5) [/favorite/0] '123': not a string. - (line 5) [/favorite/1] '456': not a string.
Anchor
You can share schemas with YAML anchor.
schema13.yaml : anchor exampletype: seq
sequence:
- &employee
type: map
mapping:
"given-name": &name
type: str
required: yes
"family-name": *name
"post":
enum:
- exective
- manager
- clerk
"supervisor": *employee
Anchor is also available in YAML document.
document13a.yaml : valid document example- &foo given-name: foo family-name: Foo post: exective - &bar given-name: bar family-name: Bar post: manager supervisor: *foo - given-name: baz family-name: Baz post: clerk supervisor: *bar - given-name: zak family-name: Zak post: clerk supervisor: *bar
$ kwalify -lf schema13.yaml document13a.yaml document13a.yaml#0: valid.
Default of Mapping
YAML allows user to specify default value of mapping.
For example, the following YAML document uses default value of mapping.
A: 10 B: 20 =: -1 # default value
This is equal to the following Ruby code.
map = ["A"=>10, "B"=>20] map.default = -1 map
Kwalify allows user to specify default rule using default value of mapping. It is useful when key names are unknown.
schema14.yaml : default rule exampletype: map
mapping:
=: # default rule
type: number
range: { max: 1, min: -1 }
document14a.yaml : valid document examplevalue1: 0 value2: 0.5 value3: -0.9
$ kwalify -lf schema14.yaml document14a.yaml document14a.yaml#0: valid.
document14b.yaml : invalid document examplevalue1: 0 value2: 1.1 value3: -2.0
$ kwalify -lf schema14.yaml document14b.yaml document14b.yaml#0: INVALID - (line 2) [/value2] '1.1': too large (> max 1). - (line 3) [/value3] '-2.0': too small (< min -1).
Merging Mappings
YAML allows user to merge mappings.
- &a1 A: 10 B: 20 - <<: *a1 # merge A: 15 # override C: 30 # add
This is equal to the following Ruby code.
a1 = {"A"=>10, "B"=>20}
tmp = {}
tmp.update(a1) # merge
tmp["A"] = 15 # override
tmp["C"] = 30 # add
This feature allows Kwalify to merge rule entries.
schema15.yaml : merging rule entries exampletype: map
mapping:
"group":
type: map
mapping:
"name": &name
type: str
required: yes
"email": &email
type: str
pattern: /@/
required: no
"user":
type: map
mapping:
"name":
<<: *name # merge
length: { max: 16 } # override
"email":
<<: *email # merge
required: yes # add
document15a.yaml : valid document examplegroup: name: foo email: foo@mail.com user: name: bar email: bar@mail.com
$ kwalify -lf schema15.yaml document15a.yaml document15a.yaml#0: valid.
document15b.yaml : invalid document examplegroup: name: foo email: foo@mail.com user: name: toooooo-looooong-name
$ kwalify -lf schema15.yaml document15b.yaml document15b.yaml#0: INVALID - (line 4) [/user] key 'email:' is required. - (line 5) [/user/name] 'toooooo-looooong-name': too long (length 21 > max 16).
Actions
Kwalify has the command-line '-a action' which perform a certain action to schema definition. Currently only the following action is supported.
- genclass-ruby
- Generate class definitions in Ruby.
- genclass-java
- Generate class definitions in Java.
In fact action name represents template filename. For example, action 'genclass-ruby' invokes template file 'kwalify/templates/genclass-ruby.eruby'.
Each action can accept some command-line properties. For example, action 'genclass-ruby' can accept the command-line properties '--module=name', '--parent=name', and so on. Type 'kwalify -h -a action' to show the list of command-line properties the action can accept.
Class Definition Generation
Command-line option '-a genclass-ruby' or '-a genclass-java' generates class definition automatically from schema definition in Ruby or Java.
Assume the following data file and schema definition.
address-book.yaml : data filegroups:
- name: family
desc: my family
- name: friend
desc: my friends
- name: business
desc: those who works together
persons:
- name: Sumire
group: family
birth: 2000-01-01
blood: A
- name: Shiina
group: friend
birth: 1995-01-01
email: shiina@mail.org
- name: Sakura
group: business
email: cherry@mail.net
phone: 012-345-6789
address-book.schema.yaml : schema definition filetype: map
classname: AddressBook
desc: address-book class
mapping:
"groups":
type: seq
sequence:
- type: map
classname: Group
desc: group class
mapping:
"name": { type: str, required: yes }
"desc": { type: str }
"persons":
type: seq
sequence:
- type: map
classname: Person
desc: person class
mapping:
"name": { type: str, required: yes }
"desc": { type: str }
"group": { type: str }
"email": { type: str, pattern: '/@/' }
"phone": { type: str }
"birth": { type: date }
"blood": { type: str, enum: [A, B, O, AB] }
Ruby Class Definition
$ kwalify -a genclass-ruby -tf address-book.schema.yaml > address-book.rb
address-book.rb : generated class definition## address-book class
class AddressBook
def initialize(hash)
@persons = (v=hash['persons']) ? v.map!{|e| e.is_a?(Person) ? e : Person.new(e)} : v
@groups = (v=hash['groups']) ? v.map!{|e| e.is_a?(Group) ? e : Group.new(e)} : v
end
attr_accessor :persons # seq
attr_accessor :groups # seq
end
## person class
class Person
def initialize(hash)
@name = hash['name']
@desc = hash['desc']
@phone = hash['phone']
@blood = hash['blood']
@group = hash['group']
@birth = hash['birth']
@email = hash['email']
end
attr_accessor :name # str
attr_accessor :desc # str
attr_accessor :phone # str
attr_accessor :blood # str
attr_accessor :group # str
attr_accessor :birth # date
attr_accessor :email # str
end
## group class
class Group
def initialize(hash)
@name = hash['name']
@desc = hash['desc']
end
attr_accessor :name # str
attr_accessor :desc # str
end
example-address-book.rb : example code of using address-book.rbrequire 'address-book'
require 'yaml'
require 'pp'
str = File.read('address-book.yaml')
ydoc = YAML.load(str)
addrbook = AddressBook.new(ydoc)
pp addrbook.groups
pp addrbook.persons
$ ruby example-address-book.rb [#<Group:0xddf24 @desc="my family", @name="family">, #<Group:0xddf10 @desc="my friends", @name="friend">, #<Group:0xdde84 @desc="those who works together", @name="business">] [#<Person:0xdefdc @birth=#<Date: 4903089/2,0,2299161>, @blood="A", @desc=nil, @email=nil, @group="family", @name="Sumire", @phone=nil>, #<Person:0xdee9c @birth=#<Date: 4899437/2,0,2299161>, @blood=nil, @desc=nil, @email="shiina@mail.org", @group="friend", @name="Shiina", @phone=nil>, #<Person:0xde8e8 @birth=nil, @blood=nil, @desc=nil, @email="cherry@mail.net", @group="business", @name="Sakura", @phone="012-345-6789">]
Command-line option '-h -a genclass-ruby' shows the commpand-line properties that template can accept.
$ kwalify -ha genclass-ruby --module=name : module name in which class defined --parent=name : parent class name --include=name : module name which all classes include
$ kwalify -a genclass-ruby --module=My --include=Kwalify::HashInterface
'Kwalify::HashInterface' is a module which make object accessible like as Hash. It is defined in 'kwalify/util/hash-interface.rb'
Java Class Definition
$ kwalify -a genclass-java -tf address-book.schema.yaml generating ./AddressBook.java...done. generating ./Person.java...done. generating ./Group.java...done.
AddressBook.java : generated class definition// generated by kwalify from address-book.schema.yaml
import java.util.*;
/**
* address-book class
*/
public class AddressBook {
private List _persons;
private List _groups;
public AddressBook() {}
public AddressBook(Map map) {
List seq;
Object obj;
if ((seq = (List)map.get("persons")) != null) {
for (int i = 0; i < seq.size(); i++) {
if ((obj = seq.get(i)) instanceof Map) {
seq.set(i, new Person((Map)obj));
}
}
}
_persons = seq;
if ((seq = (List)map.get("groups")) != null) {
for (int i = 0; i < seq.size(); i++) {
if ((obj = seq.get(i)) instanceof Map) {
seq.set(i, new Group((Map)obj));
}
}
}
_groups = seq;
}
public List getPersons() { return _persons; }
public void setPersons(List persons_) { _persons = persons_; }
public List getGroups() { return _groups; }
public void setGroups(List groups_) { _groups = groups_; }
}
Group.java : generated class definition// generated by kwalify from address-book.schema.yaml
import java.util.*;
/**
* group class
*/
public class Group {
private String _name;
private String _desc;
public Group() {}
public Group(Map map) {
_name = (String)map.get("name");
_desc = (String)map.get("desc");
}
public String getName() { return _name; }
public void setName(String name_) { _name = name_; }
public String getDesc() { return _desc; }
public void setDesc(String desc_) { _desc = desc_; }
}
Person.java : generated class definition// generated by kwalify from address-book.schema.yaml
import java.util.*;
/**
* person class
*/
public class Person {
private String _name;
private String _desc;
private String _phone;
private String _blood;
private String _group;
private Date _birth;
private String _email;
public Person() {}
public Person(Map map) {
_name = (String)map.get("name");
_desc = (String)map.get("desc");
_phone = (String)map.get("phone");
_blood = (String)map.get("blood");
_group = (String)map.get("group");
_birth = (Date)map.get("birth");
_email = (String)map.get("email");
}
public String getName() { return _name; }
public void setName(String name_) { _name = name_; }
public String getDesc() { return _desc; }
public void setDesc(String desc_) { _desc = desc_; }
public String getPhone() { return _phone; }
public void setPhone(String phone_) { _phone = phone_; }
public String getBlood() { return _blood; }
public void setBlood(String blood_) { _blood = blood_; }
public String getGroup() { return _group; }
public void setGroup(String group_) { _group = group_; }
public Date getBirth() { return _birth; }
public void setBirth(Date birth_) { _birth = birth_; }
public String getEmail() { return _email; }
public void setEmail(String email_) { _email = email_; }
}
ExampleAddressBook.java : example code of using *.javaimport java.util.*;
import kwalify.*;
public class ExampleAddressBook {
public static void main(String args[]) throws Exception {
// read schema
String schema_str = Util.readFile("address-book.schema.yaml");
schema_str = Util.untabify(schema_str);
Object schema = new YamlParser(schema_str).parse();
// read document file
String document_str = Util.readFile("address-book.yaml");
document_str = Util.untabify(document_str);
YamlParser parser = new YamlParser(document_str);
Object document = parser.parse();
// create address book object
AddressBook addrbook = new AddressBook((Map)document);
// show groups
List groups = addrbook.getGroups();
if (groups != null) {
for (Iterator it = groups.iterator(); it.hasNext(); ) {
Group group = (Group)it.next();
System.out.println("group name: " + group.getName());
System.out.println("group desc: " + group.getDesc());
System.out.println();
}
}
// show persons
List persons = addrbook.getPersons();
if (persons != null) {
for (Iterator it = persons.iterator(); it.hasNext(); ) {
Person person = (Person)it.next();
System.out.println("person name: " + person.getName());
System.out.println("person group: " + person.getGroup());
System.out.println("person email: " + person.getEmail());
System.out.println("person phone: " + person.getPhone());
System.out.println("person blood: " + person.getBlood());
System.out.println("person birth: " + person.getBirth());
System.out.println();
}
}
}
}
$ javac -classpath '.:kwalify.jar' *.java $ java -classpath '.:kwalify.jar' ExampleAddressBook group name: family group desc: my family group name: friend group desc: my friends group name: business group desc: those who works together person name: Sumire person group: family person email: null person phone: null person blood: A person birth: Tue Feb 01 00:00:00 JST 2000 person name: Shiina person group: friend person email: shiina@mail.org person phone: null person blood: null person birth: Wed Feb 01 00:00:00 JST 1995 person name: Sakura person group: business person email: cherry@mail.net person phone: 012-345-6789 person blood: null person birth: null
Command-line option '-h -a genclass-java' shows the commpand-line properties that template can accept.
$ kwalify -ha genclass-java --package=name : package name --extends=name : class name to extend --implements=name,... : interface names to implement --dir=path : directory to locate output file --basedir=path : base directory to locate output file
$ kwalify -a genclass-java --package=com.example.my --implements=Serializable --basedir=src
Usage of Kwalify
Usage in Command-Line
### kwalify-ruby $ kwalify -f schema.yaml document.yaml [document2.yaml ...] ### kwalify-java $ java -classpath kwalify.jar kwalify.Main -f schema.yaml document.yaml [document2.yaml ...]
### kwalify-ruby $ kwalify -m schema.yaml [schema2.yaml ...] ### kwalify-java $ java -classpath kwalify.jar kwalify.Main -m schema.yaml [schema2.yaml ...]
Command-line options:
-
-h,--help - Print help message.
-
-v - Print version.
-
-s - Silent mode.
-
-f schema.yaml - Specify schema definition file.
-
-m - Meta-validation of schema definition.
-
-t - Expand tab characters to spaces automatically.
-
-l - Show linenumber on which error found.
-
-E - Show errors in Emacs-compatible style (implies '-l' option).
-
-a action - Do action. Currently supported action is 'genclass-ruby' and 'genclass-java'. Try '-ha action' to get help about the action.
Notice that the command-line option -l is an experimental feature, for kwalify command use original YAML parser instead of Syck parser when this option is specified.
If you are an Emacs user, try -E option that show errors in format which Emacs can parse and jump to errors.
You can use C-x ` (next-error) to jump into errors.
Usage in Ruby Script
The followings are example scripts for Ruby.
require 'kwalify'
## parse schema definition and create validator
schema = YAML.load_file('schema.yaml')
validator = Kwalify::Validator.new(schema) # raises Kwalify::SchemaError if wrong
## validate YAML document
document = YAML.load_file('document.yaml')
error_list = validator.validate(document)
unless error_list.empty?
error_list.each do |error| # error is instance of Kwalify::ValidationError
puts "[#{error.path}] #{error.message}"
end
end
require 'kwalify'
## parse schema definition and create validator
schema = YAML.load_file('schema.yaml')
validator = Kwalify::Validator.new(schema) # raises Kwalify::SchemaError if wrong
## parse YAML document with Kwalify's parser
str = File.read('document.yaml')
parser = Kwalify::Parser.new(str)
document = parser.parse()
## validate document and show errors
error_list = validator.validate(document)
unless error_list.empty?
parser.set_errors_linenum(error_list) # set linenum on error
error_list.sort.each do |error|
puts "(line %d)[%s] %s" % [error.linenum, error.path, error.message]
end
end
Kwalify's YAML parser is experimental. You should notice that Kwalify's YAML parser is limited only for basic syntax of YAML.
Usage in Java Program
The followings are example programs of Java.
import kwalify.*;
public class Test {
public static void main(String[] args) throws Exception {
// read schema
String schema_str = Util.readFile("schema.yaml");
schema_str = Util.untabify(schema_str);
Object schema = new YamlParser(schema_str).parse();
// read document file
String document_str = Util.readFile("document.yaml");
document_str = Util.untabify(document_str);
YamlParser parser = new YamlParser(document_str);
Object document = parser.parse();
// create validator and validate
Validator validator = new Validator(schema);
List errors = validator.validate(document);
// show errors
if (errors != null && errors.size() > 0) {
parser.setErrorsLineNumber(errors);
Collections.sort(errors);
for (Iterator it = errors.iterator(); it.hasNext(); ) {
ValidationException error = (ValidationException)it.next();
int linenum = error.getLineNumber();
String path = error.getPath();
String mesg = error.getMessage();
System.out.println("- " + linenum + ": [" + path + "] " + mesg);
}
}
}
}