jcon 0.1
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.
- data/LICENSE +21 -0
- data/Manifest +15 -0
- data/README +41 -0
- data/TODO +26 -0
- data/jcon.gemspec +64 -0
- data/lib/jcon.rb +5 -0
- data/lib/jcon/conformance.rb +57 -0
- data/lib/jcon/dictionary.rb +69 -0
- data/lib/jcon/matchers/jcon_matchers.rb +26 -0
- data/lib/jcon/parser.rb +127 -0
- data/lib/jcon/types.rb +67 -0
- data/spec/conformance_spec.rb +162 -0
- data/spec/jcon_spec.rb +10 -0
- data/spec/matchers_spec.rb +48 -0
- data/spec/parser_spec.rb +110 -0
- data/spec/spec_helper.rb +2 -0
- metadata +74 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2008 Oliver Steele
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/Manifest
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
lib/jcon/conformance.rb
|
2
|
+
lib/jcon/dictionary.rb
|
3
|
+
lib/jcon/matchers/jcon_matchers.rb
|
4
|
+
lib/jcon/parser.rb
|
5
|
+
lib/jcon/types.rb
|
6
|
+
lib/jcon.rb
|
7
|
+
LICENSE
|
8
|
+
Manifest
|
9
|
+
README
|
10
|
+
spec/conformance_spec.rb
|
11
|
+
spec/jcon_spec.rb
|
12
|
+
spec/matchers_spec.rb
|
13
|
+
spec/parser_spec.rb
|
14
|
+
spec/spec_helper.rb
|
15
|
+
TODO
|
data/README
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
= JCon -- JavaScript Type Conformance Checking
|
2
|
+
|
3
|
+
Test whether a parsed JSON type conforms to a type specification, read
|
4
|
+
from a string or file that matches the proposed ECMAScript 4.0 type
|
5
|
+
syntax (PDF[http://www.ecmascript.org/es4/spec/overview.pdf]).
|
6
|
+
|
7
|
+
|
8
|
+
== Install
|
9
|
+
|
10
|
+
gem install rcon
|
11
|
+
|
12
|
+
== Usage
|
13
|
+
|
14
|
+
type = JCON::parse "[string, int]"
|
15
|
+
type.contains?(['a', 1]) # => true
|
16
|
+
type.contains?(['a', 'b']) # => false
|
17
|
+
type.contains?(['a', 1, 2]) # => true
|
18
|
+
|
19
|
+
type = JCON::parse "type S = (string, int); {a: [S], b: int}"
|
20
|
+
type.contains?({:a => [1, 'b'], :b => 2}) # => true
|
21
|
+
|
22
|
+
== RSpec Matcher
|
23
|
+
|
24
|
+
[1, 'xyzzy'].should conform_to_js('[int, string]')
|
25
|
+
[1, 2, 'xyzzy'].should_not conform_to_js('[int, string]')
|
26
|
+
{:x => 1}.should conform_to_js('{x: int}')
|
27
|
+
|
28
|
+
Use this with the "JavaScriptFu"[http://osteele.com/archives/2008/04/javascript-fu-rails-plugin] plugin to test JSON arguments:
|
29
|
+
|
30
|
+
'<script>fn("id", {x:1, y:2}, true)</script>'.should call_js('fn') do |args|
|
31
|
+
args[0].should conform_to_js('string')
|
32
|
+
args[1].should conform_to_js('{x:int, y:int}')
|
33
|
+
args[2].should conform_to_js('boolean')
|
34
|
+
# or:
|
35
|
+
args.should conform_to_js('[string, {x:int, y:int}, boolean]')
|
36
|
+
end
|
37
|
+
|
38
|
+
== License
|
39
|
+
|
40
|
+
Copyright 2008 by Oliver Steele. All rights reserved. Released under
|
41
|
+
the MIT License.
|
data/TODO
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
= JCon TODO
|
2
|
+
|
3
|
+
Next:
|
4
|
+
* docs -- add the README
|
5
|
+
* check the spec
|
6
|
+
* add method to display path to mismatch
|
7
|
+
* rename SimpleType
|
8
|
+
|
9
|
+
Error detection:
|
10
|
+
* circular references
|
11
|
+
* duplicate definitions
|
12
|
+
|
13
|
+
Test cases:
|
14
|
+
* recursive types
|
15
|
+
|
16
|
+
Parser:
|
17
|
+
* can property name be string?
|
18
|
+
|
19
|
+
Conformance:
|
20
|
+
* byte
|
21
|
+
|
22
|
+
Future:
|
23
|
+
* function(this:T | T | T= | ... | ...[T]) : T | void
|
24
|
+
* 'class'
|
25
|
+
* js implementation
|
26
|
+
* includes
|
data/jcon.gemspec
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
# Gem::Specification for Jcon-0.1
|
3
|
+
# Originally generated by Echoe
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{jcon}
|
7
|
+
s.version = "0.1"
|
8
|
+
s.date = %q{2008-04-16}
|
9
|
+
s.summary = %q{Test JSON values against a schema.}
|
10
|
+
s.email = %q{steele@osteele.com}
|
11
|
+
s.homepage = %q{http://jcon.rubyforge.org}
|
12
|
+
s.rubyforge_project = %q{jcon}
|
13
|
+
s.description = %q{Test JSON values for conformance with ECMAScript 4.0 types.}
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.authors = ["Oliver Steele"]
|
16
|
+
s.files = ["lib/jcon/conformance.rb", "lib/jcon/dictionary.rb", "lib/jcon/matchers/jcon_matchers.rb", "lib/jcon/parser.rb", "lib/jcon/types.rb", "lib/jcon.rb", "LICENSE", "Manifest", "README", "spec/conformance_spec.rb", "spec/jcon_spec.rb", "spec/matchers_spec.rb", "spec/parser_spec.rb", "spec/spec_helper.rb", "TODO", "jcon.gemspec"]
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Jcon", "--main", "README", "--title", "JCON: Test JSON values against a schema", "--main", "README", "--exclude", "spec/.*"]
|
18
|
+
s.extra_rdoc_files = ["README", "TODO", "LICENSE"]
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# # Original Rakefile source (requires the Echoe gem):
|
23
|
+
#
|
24
|
+
# require 'rubygems'
|
25
|
+
# require 'echoe'
|
26
|
+
# require 'spec/rake/spectask'
|
27
|
+
#
|
28
|
+
# PKG_VERSION = '0.1'
|
29
|
+
#
|
30
|
+
# task :test => :spec
|
31
|
+
#
|
32
|
+
# Echoe.new('jcon', PKG_VERSION) do |p|
|
33
|
+
# p.summary = "Test JSON values against a schema."
|
34
|
+
# p.url = 'http://jcon.rubyforge.org'
|
35
|
+
# p.description = <<-EOF
|
36
|
+
# Test JSON values for conformance with ECMAScript 4.0 types.
|
37
|
+
# EOF
|
38
|
+
# p.author = 'Oliver Steele'
|
39
|
+
# p.email = 'steele@osteele.com'
|
40
|
+
# p.ignore_pattern = /^(.git|.*#.*#)$/
|
41
|
+
# p.test_pattern = 'test/*_test.rb'
|
42
|
+
# p.rdoc_pattern = /^(lib|bin|tasks|ext)|^README|^CHANGES|^TODO|^LICENSE$/
|
43
|
+
# p.eval = proc do |s|
|
44
|
+
# s.rdoc_options <<
|
45
|
+
# '--title' << "JCON: #{s.summary.sub(/.$/,'')}" <<
|
46
|
+
# '--main' << 'README' <<
|
47
|
+
# '--exclude' << 'spec/.*'
|
48
|
+
# s.extra_rdoc_files = ['README', 'TODO', 'LICENSE']
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# desc "Really a replacement for echoe's 'install', which bombs on my machine"
|
53
|
+
# task :reinstall => [:clean, :package, :uninstall] do
|
54
|
+
# system "sudo gem install pkg/*.gem"
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# desc "Run all specs"
|
58
|
+
# Spec::Rake::SpecTask.new do |t|
|
59
|
+
# t.spec_files = FileList['spec/*_spec.rb']
|
60
|
+
# if ENV['RCOV']
|
61
|
+
# t.rcov = true
|
62
|
+
# t.rcov_opts = ['--exclude', 'spec\/spec']
|
63
|
+
# end
|
64
|
+
# end
|
data/lib/jcon.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module JCON
|
2
|
+
module Types
|
3
|
+
class SimpleType < Type
|
4
|
+
def contains?(value)
|
5
|
+
if not @test and context and@context[name]
|
6
|
+
return context[name].contains?(value)
|
7
|
+
end
|
8
|
+
raise "No definition for #{self}" unless @test
|
9
|
+
@test.call(value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class OptionalType < Type
|
14
|
+
def contains?(value)
|
15
|
+
value.nil? or type.contains?(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class RequiredType < Type
|
20
|
+
def contains?(value)
|
21
|
+
!value.nil? and type.contains?(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ListType < Type
|
26
|
+
def contains?(value)
|
27
|
+
return false unless value.is_a?(Array)
|
28
|
+
return false unless value.length >= types.length
|
29
|
+
value.each_with_index do |x, i|
|
30
|
+
return false unless types[[i, types.length-1].min].contains?(x)
|
31
|
+
end
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class UnionType < Type
|
37
|
+
def contains?(value)
|
38
|
+
types.any? do |type| type.contains?(value) end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class RecordType < Type
|
43
|
+
def contains?(value)
|
44
|
+
return false unless value.is_a?(Hash)
|
45
|
+
value.each do |k, v|
|
46
|
+
type = properties["#{k}".intern]
|
47
|
+
return false unless type
|
48
|
+
return false unless type.contains?(v)
|
49
|
+
end
|
50
|
+
properties.each do |k, _|
|
51
|
+
return false unless value.include?(k) or value.include?(k.to_s)
|
52
|
+
end
|
53
|
+
true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'types')
|
2
|
+
|
3
|
+
module JCON
|
4
|
+
class Dictionary
|
5
|
+
include Types
|
6
|
+
|
7
|
+
attr_reader :parent, :definitions
|
8
|
+
attr_accessor :start
|
9
|
+
|
10
|
+
def initialize(parent=BUILTINS)
|
11
|
+
@parent = parent
|
12
|
+
@definitions = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def deftype(name, type=nil, &block)
|
16
|
+
return deftype(name, SimpleType.new(name, &block)) if block
|
17
|
+
case type
|
18
|
+
when nil
|
19
|
+
deftype(name, Kernel.const_get(name))
|
20
|
+
when Class
|
21
|
+
deftype(name) do |x| x.nil? or x.is_a?(type) end
|
22
|
+
when Array
|
23
|
+
deftype(name) do |x| type.include?(x) end
|
24
|
+
when Type
|
25
|
+
name = name.intern if name.is_a?(String)
|
26
|
+
@definitions[name] = type
|
27
|
+
@start ||= type
|
28
|
+
else
|
29
|
+
raise "invalid arguments"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](name)
|
34
|
+
@definitions[name] || (parent && parent[name])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class DefaultDictionary < Dictionary
|
39
|
+
def initialize
|
40
|
+
super(nil)
|
41
|
+
add_builtin_definitions
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_builtin_definitions
|
45
|
+
deftype(:*) do true end
|
46
|
+
deftype(:null) do |x| x.nil? end
|
47
|
+
deftype(:undefined) do |x| x.nil? end
|
48
|
+
deftype(:Object) do true end
|
49
|
+
deftype(:Array)
|
50
|
+
deftype(:Date)
|
51
|
+
deftype(:RegExp, Regexp)
|
52
|
+
deftype(:Boolean, [false,true,nil])
|
53
|
+
deftype(:String)
|
54
|
+
deftype(:Number, Numeric)
|
55
|
+
deftype(:boolean, [false,true])
|
56
|
+
deftype(:string) do |x| x.is_a?(String) end
|
57
|
+
deftype(:int) do |x| x.is_a?(Integer) end
|
58
|
+
deftype(:uint) do |x| x.is_a?(Integer) and x > 0 end
|
59
|
+
deftype(:double) do |x| x.is_a?(Numeric) end
|
60
|
+
deftype(:decimal) do |x| x.is_a?(Numeric) end
|
61
|
+
deftype :AnyString, union(:string, String)
|
62
|
+
deftype :AnyBoolean, union(:boolean, :Boolean)
|
63
|
+
deftype :AnyNumber, union(:byte, :int, :uint, :double, :decimal,:Number)
|
64
|
+
deftype :FloatNumber, union(:double, :decimal)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
BUILTINS = DefaultDictionary.new
|
69
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module JCON
|
2
|
+
module Matchers
|
3
|
+
class ConformToJSType #:nodoc:
|
4
|
+
def initialize(type_or_text)
|
5
|
+
type = type_or_text
|
6
|
+
type = JCON.parse(type) if String === type
|
7
|
+
@expected = type
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(actual)
|
11
|
+
@expected.contains?(actual)
|
12
|
+
end
|
13
|
+
|
14
|
+
def failure_message; "should #{description}, but did not"; end
|
15
|
+
def negative_failure_message; "should not #{description}, but did"; end
|
16
|
+
|
17
|
+
def description
|
18
|
+
"conform to type #{@expected}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def conform_to_js(type_or_text)
|
23
|
+
return ConformToJSType.new(type_or_text)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/jcon/parser.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module JCON
|
4
|
+
def self.parse(source)
|
5
|
+
Parser.parse(source).start
|
6
|
+
end
|
7
|
+
|
8
|
+
class Parser < StringScanner
|
9
|
+
include Types
|
10
|
+
|
11
|
+
COMMENT = %r{//.*|/\*(?:.|[\r\n])*?\*/}
|
12
|
+
WS_CHAR = /[\s\r\n]/
|
13
|
+
IGNORE = /(?:#{COMMENT}|#{WS_CHAR})+/
|
14
|
+
IDENTIFIER = /[\w_$][\w\d_$]*/
|
15
|
+
|
16
|
+
def self.parse(source)
|
17
|
+
self.new(source).parse
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :dictionary
|
21
|
+
|
22
|
+
def initialize(source)
|
23
|
+
super(source)
|
24
|
+
@dictionary = Dictionary.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse
|
28
|
+
reset
|
29
|
+
until eos?
|
30
|
+
case
|
31
|
+
when skip(IGNORE)
|
32
|
+
when skip(/;/)
|
33
|
+
;
|
34
|
+
when scan(/type/)
|
35
|
+
parse_deftype
|
36
|
+
else
|
37
|
+
type = parse_type
|
38
|
+
dictionary.start = type
|
39
|
+
while skip(IGNORE) || skip(/;/); end
|
40
|
+
parse_error "expected end of input" unless eos?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
dictionary
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_deftype
|
47
|
+
name = expect('identifier', IDENTIFIER)
|
48
|
+
expect('=', /=/)
|
49
|
+
type = parse_type
|
50
|
+
dictionary.deftype(name.intern, type)
|
51
|
+
sscan(/;/)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_type
|
55
|
+
skip(IGNORE)
|
56
|
+
type = case
|
57
|
+
when id = scan(IDENTIFIER)
|
58
|
+
simple_type(id)
|
59
|
+
when scan(/\*/)
|
60
|
+
simple_type('*')
|
61
|
+
when scan(/\[/)
|
62
|
+
types = parse_types_until(/\]/)
|
63
|
+
list(*types)
|
64
|
+
when scan(/\(/)
|
65
|
+
types = parse_types_until(/\)/)
|
66
|
+
union(*types)
|
67
|
+
when scan(/\{/)
|
68
|
+
parse_structure_type
|
69
|
+
else
|
70
|
+
parse_error
|
71
|
+
end
|
72
|
+
type.context = dictionary
|
73
|
+
add_modifiers(type)
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_modifiers(type)
|
77
|
+
while sscan(/[!?]/)
|
78
|
+
case self[0]
|
79
|
+
when '!'
|
80
|
+
type = required(type)
|
81
|
+
when '?'
|
82
|
+
type = optional(type)
|
83
|
+
end
|
84
|
+
type.context = dictionary
|
85
|
+
end
|
86
|
+
type
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_types_until(stop_pattern)
|
90
|
+
types = []
|
91
|
+
while true
|
92
|
+
break if sscan(stop_pattern)
|
93
|
+
expect('comma', /,/) if types.any?
|
94
|
+
types << parse_type
|
95
|
+
end
|
96
|
+
types
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_structure_type
|
100
|
+
map = {}
|
101
|
+
while true
|
102
|
+
break if sscan(/\}/)
|
103
|
+
expect('comma', /,/) if map.any?
|
104
|
+
name = expect('id', IDENTIFIER)
|
105
|
+
expect(':', /:/)
|
106
|
+
type = parse_type
|
107
|
+
map[name.intern] = type
|
108
|
+
end
|
109
|
+
RecordType.new(map)
|
110
|
+
end
|
111
|
+
|
112
|
+
def expect(name, pattern)
|
113
|
+
value = sscan(pattern)
|
114
|
+
parse_error("expected #{name}") unless value
|
115
|
+
value
|
116
|
+
end
|
117
|
+
|
118
|
+
def sscan(pattern)
|
119
|
+
skip(IGNORE)
|
120
|
+
scan(pattern)
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_error(msg='unexpected token')
|
124
|
+
raise "#{msg} at '#{skip(IGNORE); peek(20)}'"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/jcon/types.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module JCON
|
2
|
+
module Types
|
3
|
+
def self.to_type(value)
|
4
|
+
value = SimpleType.new(value) unless value.is_a?(Type)
|
5
|
+
value
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.to_types(values)
|
9
|
+
values.map { |value| to_type(value) }
|
10
|
+
end
|
11
|
+
|
12
|
+
class Type
|
13
|
+
attr_accessor :context
|
14
|
+
def inspect; to_s; end
|
15
|
+
end
|
16
|
+
|
17
|
+
class SimpleType < Type
|
18
|
+
attr_reader :name
|
19
|
+
def initialize(name, &block)
|
20
|
+
name = name.intern if name.is_a?(String)
|
21
|
+
@name = name
|
22
|
+
@test = block
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s; name.to_s; end
|
26
|
+
end
|
27
|
+
|
28
|
+
class OptionalType < Type
|
29
|
+
attr_reader :type
|
30
|
+
def initialize(type); @type = type; end
|
31
|
+
def to_s; "#{type}?"; end
|
32
|
+
end
|
33
|
+
|
34
|
+
class RequiredType < Type
|
35
|
+
attr_reader :type
|
36
|
+
def initialize(type); @type = type; end
|
37
|
+
def to_s; "#{type}!"; end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ListType < Type
|
41
|
+
attr_reader :types
|
42
|
+
def initialize(types); @types = Types.to_types(types); end
|
43
|
+
def to_s; "[#{types.join(', ')}]"; end
|
44
|
+
end
|
45
|
+
|
46
|
+
class UnionType < Type
|
47
|
+
attr_reader :types
|
48
|
+
def initialize(types); @types = Types.to_types(types); end
|
49
|
+
def to_s; "(#{types.join(', ')})"; end
|
50
|
+
end
|
51
|
+
|
52
|
+
class RecordType < Type
|
53
|
+
attr_reader :properties
|
54
|
+
def initialize(properties);
|
55
|
+
@properties = properties
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s; "{#{properties.map { |k,v| "#{k}: #{v}"}.join(', ')}}"; end
|
59
|
+
end
|
60
|
+
|
61
|
+
def simple_type(*args); SimpleType.new(*args); end
|
62
|
+
def required(*args); RequiredType.new(*args); end
|
63
|
+
def optional(*args); OptionalType.new(*args); end
|
64
|
+
def list(*args); ListType.new(args); end
|
65
|
+
def union(*args); UnionType.new(args); end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe JCON do
|
4
|
+
describe :conforms? do
|
5
|
+
it "should test the Any type" do
|
6
|
+
type = JCON::parse('*')
|
7
|
+
type.contains?(false).should == true
|
8
|
+
type.contains?(0).should == true
|
9
|
+
type.contains?(1).should == true
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should test the null type" do
|
13
|
+
type = JCON::parse('null')
|
14
|
+
type.contains?(false).should == false
|
15
|
+
type.contains?(0).should == false
|
16
|
+
type.contains?(nil).should == true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should test the undefined type" do
|
20
|
+
type = JCON::parse('undefined')
|
21
|
+
type.contains?(false).should == false
|
22
|
+
type.contains?(0).should == false
|
23
|
+
type.contains?(nil).should == true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should test string types" do
|
27
|
+
type = JCON::parse('string')
|
28
|
+
type.contains?(1).should == false
|
29
|
+
type.contains?('s').should == true
|
30
|
+
type.contains?(nil).should == false
|
31
|
+
|
32
|
+
type = JCON::parse('String')
|
33
|
+
type.contains?(1).should == false
|
34
|
+
type.contains?('s').should == true
|
35
|
+
type.contains?(nil).should == true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should test numeric types" do
|
39
|
+
type = JCON::parse('Number')
|
40
|
+
type.contains?(1).should == true
|
41
|
+
type.contains?('s').should == false
|
42
|
+
type.contains?(nil).should == true
|
43
|
+
|
44
|
+
type = JCON::parse('int')
|
45
|
+
type.contains?(1).should == true
|
46
|
+
type.contains?(1.5).should == false
|
47
|
+
type.contains?('s').should == false
|
48
|
+
type.contains?(nil).should == false
|
49
|
+
|
50
|
+
type = JCON::parse('uint')
|
51
|
+
type.contains?(1).should == true
|
52
|
+
type.contains?(1.5).should == false
|
53
|
+
type.contains?(-1).should == false
|
54
|
+
type.contains?(nil).should == false
|
55
|
+
|
56
|
+
type = JCON::parse('double')
|
57
|
+
type.contains?(1).should == true
|
58
|
+
type.contains?(1.5).should == true
|
59
|
+
type.contains?(nil).should == false
|
60
|
+
|
61
|
+
type = JCON::parse('decimal')
|
62
|
+
type.contains?(1).should == true
|
63
|
+
type.contains?(1.5).should == true
|
64
|
+
type.contains?(nil).should == false
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should test object types" do
|
68
|
+
type = JCON::parse('Object')
|
69
|
+
type.contains?(1).should == true
|
70
|
+
type.contains?('s').should == true
|
71
|
+
type.contains?([]).should == true
|
72
|
+
type.contains?({}).should == true
|
73
|
+
type.contains?(nil).should == true
|
74
|
+
|
75
|
+
type = JCON::parse('Array')
|
76
|
+
type.contains?(1).should == false
|
77
|
+
type.contains?('s').should == false
|
78
|
+
type.contains?([]).should == true
|
79
|
+
type.contains?({}).should == false
|
80
|
+
type.contains?(nil).should == true
|
81
|
+
|
82
|
+
type = JCON::parse('Date')
|
83
|
+
type.contains?(1).should == false
|
84
|
+
type.contains?('s').should == false
|
85
|
+
type.contains?(Date.new).should == true
|
86
|
+
type.contains?({}).should == false
|
87
|
+
type.contains?(nil).should == true
|
88
|
+
|
89
|
+
type = JCON::parse('RegExp')
|
90
|
+
type.contains?(1).should == false
|
91
|
+
type.contains?('s').should == false
|
92
|
+
type.contains?(/s/).should == true
|
93
|
+
type.contains?({}).should == false
|
94
|
+
type.contains?(nil).should == true
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should test optional types" do
|
98
|
+
type = JCON::parse('int?')
|
99
|
+
type.contains?(1).should == true
|
100
|
+
type.contains?(nil).should == true
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should test required types" do
|
104
|
+
type = JCON::parse('Object!')
|
105
|
+
type.contains?(1).should == true
|
106
|
+
type.contains?(nil).should == false
|
107
|
+
|
108
|
+
type = JCON::parse('Number!')
|
109
|
+
type.contains?(1).should == true
|
110
|
+
type.contains?(nil).should == false
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should test union types" do
|
114
|
+
type = JCON::parse('(string, boolean)')
|
115
|
+
type.contains?('s').should == true
|
116
|
+
type.contains?(true).should == true
|
117
|
+
type.contains?(1).should == false
|
118
|
+
type.contains?(nil).should == false
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should test list types" do
|
122
|
+
type = JCON::parse('[string, boolean]')
|
123
|
+
type.contains?('s').should == false
|
124
|
+
type.contains?([]).should == false
|
125
|
+
type.contains?([1]).should == false
|
126
|
+
type.contains?([1,2]).should == false
|
127
|
+
type.contains?([1,2,3]).should == false
|
128
|
+
type.contains?(['s']).should == false
|
129
|
+
type.contains?(['s',2]).should == false
|
130
|
+
type.contains?([1,true]).should == false
|
131
|
+
type.contains?(['s',true]).should == true
|
132
|
+
type.contains?(['s',true,true]).should == true
|
133
|
+
type.contains?(['s',true,3]).should == false
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should test record types" do
|
137
|
+
type = JCON::parse('{a:int, b:string}')
|
138
|
+
type.contains?('s').should == false
|
139
|
+
|
140
|
+
type.contains?({:a => 1}).should == false
|
141
|
+
type.contains?({:b => 's'}).should == false
|
142
|
+
type.contains?({:a => 1, :b => 's'}).should == true
|
143
|
+
type.contains?({:a => 's', :b => 's'}).should == false
|
144
|
+
type.contains?({:a => 1, :b => 2}).should == false
|
145
|
+
type.contains?({:a => 1, :b => 's', :c => false}).should == false
|
146
|
+
|
147
|
+
type.contains?({'a' => 1}).should == false
|
148
|
+
type.contains?({'b' => 's'}).should == false
|
149
|
+
type.contains?({'a' => 1, 'b' => 's'}).should == true
|
150
|
+
type.contains?({'a' => 's', 'b' => 's'}).should == false
|
151
|
+
type.contains?({'a' => 1, 'b' => 2}).should == false
|
152
|
+
type.contains?({'a' => 1, 'b' => 's', :c => false}).should == false
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should test type definitions"
|
156
|
+
|
157
|
+
it "should raise an error when for unknown" do
|
158
|
+
type = JCON::parse('S')
|
159
|
+
lambda { type.contains?('s') }.should raise_error('No definition for S')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/spec/jcon_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe JCON::Matchers do
|
4
|
+
include JCON::Matchers
|
5
|
+
|
6
|
+
describe :conforms_to_js do
|
7
|
+
it "should test basic types" do
|
8
|
+
1.should conform_to_js('int')
|
9
|
+
1.should_not conform_to_js('string')
|
10
|
+
's'.should conform_to_js('string')
|
11
|
+
's'.should_not conform_to_js('int')
|
12
|
+
lambda { 1.should conform_to_js('string') }.should raise_error('should conform to type string, but did not')
|
13
|
+
lambda { 1.should_not conform_to_js('int') }.should raise_error('should not conform to type int, but did')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should test nullability" do
|
17
|
+
1.should conform_to_js('int')
|
18
|
+
nil.should_not conform_to_js('int')
|
19
|
+
1.should conform_to_js('Number')
|
20
|
+
nil.should conform_to_js('Number')
|
21
|
+
1.should conform_to_js('int!')
|
22
|
+
nil.should_not conform_to_js('int!')
|
23
|
+
1.should conform_to_js('Number!')
|
24
|
+
nil.should_not conform_to_js('Number!')
|
25
|
+
1.should conform_to_js('int?')
|
26
|
+
nil.should conform_to_js('int?')
|
27
|
+
1.should conform_to_js('Number?')
|
28
|
+
nil.should conform_to_js('Number?')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should test more complex cases" do
|
32
|
+
[[1], 2].should conform_to_js('[Array, (int, boolean)]')
|
33
|
+
[[1], true].should conform_to_js('[Array, (int, boolean)]')
|
34
|
+
[[1], 2, true].should conform_to_js('[Array, (int, boolean)]')
|
35
|
+
[].should_not conform_to_js('[Array, (int, boolean)]')
|
36
|
+
[1, 2].should_not conform_to_js('[Array, (int, boolean)]')
|
37
|
+
[[1], nil].should_not conform_to_js('[Array, (int, boolean)]')
|
38
|
+
{'x' => 1, 'y' => 2, 'z' => 3}.should conform_to_js('{x: double, y: double, z: double?}')
|
39
|
+
[[[1], 2], {'x' => 1, 'y' => 2, 'z' => 3}].should conform_to_js('[[Array, (int, boolean)], {x: double, y: double, z: double?}]')
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should work with the examples in the README" do
|
43
|
+
[1, 'xyzzy'].should conform_to_js('[int, string]')
|
44
|
+
[1, 2, 'xyzzy'].should_not conform_to_js('[int, string]')
|
45
|
+
{:x => 1}.should conform_to_js('{x: int}')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe JCON::Parser do
|
4
|
+
describe :parse do
|
5
|
+
it "should parse a bare type" do
|
6
|
+
type = JCON::Parser.parse('string').start
|
7
|
+
type.should be_an_instance_of(JCON::Types::SimpleType)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should parse a union type" do
|
11
|
+
type = JCON::Parser.parse('(string, String)').start
|
12
|
+
type.should be_an_instance_of(JCON::Types::UnionType)
|
13
|
+
type.types.length.should == 2
|
14
|
+
type.types[0].name.should == :string
|
15
|
+
type.types[1].name.should == :String
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should parse a list type" do
|
19
|
+
type = JCON::Parser.parse('[string, String]').start
|
20
|
+
type.should be_an_instance_of(JCON::Types::ListType)
|
21
|
+
type.types.length.should == 2
|
22
|
+
type.types[0].name.should == :string
|
23
|
+
type.types[1].name.should == :String
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should parse a required type" do
|
27
|
+
type = JCON::Parser.parse('string!').start
|
28
|
+
type.should be_an_instance_of(JCON::Types::RequiredType)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should parse an optional type" do
|
32
|
+
type = JCON::Parser.parse('type T = string?')[:T]
|
33
|
+
type.should be_an_instance_of(JCON::Types::OptionalType)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should parse a record type" do
|
37
|
+
type = JCON::Parser.parse('type T = {a:string?, b:int}')[:T]
|
38
|
+
type.should be_an_instance_of(JCON::Types::RecordType)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should parse a basic type definition" do
|
42
|
+
type = JCON::Parser.parse('type T = string')[:T]
|
43
|
+
type.should be_an_instance_of(JCON::Types::SimpleType)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should parse two type definitions" do
|
47
|
+
dict = JCON::Parser.parse('type T = string; type U = String;')
|
48
|
+
dict.definitions.length.should == 2
|
49
|
+
dict[:U].should be_an_instance_of(JCON::Types::SimpleType)
|
50
|
+
dict[:T].should be_an_instance_of(JCON::Types::SimpleType)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should parse a union type definition" do
|
54
|
+
type = JCON::Parser.parse('type T = (string, String)')[:T]
|
55
|
+
type.should be_an_instance_of(JCON::Types::UnionType)
|
56
|
+
type.types.length.should == 2
|
57
|
+
type.types[0].name.should == :string
|
58
|
+
type.types[1].name.should == :String
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should ignore extraneous semicolons" do
|
62
|
+
dict = JCON::Parser.parse(';type T = string')
|
63
|
+
dict.definitions.length.should == 1
|
64
|
+
dict = JCON::Parser.parse(';;type T = string')
|
65
|
+
dict.definitions.length.should == 1
|
66
|
+
dict = JCON::Parser.parse('type T = string;')
|
67
|
+
dict.definitions.length.should == 1
|
68
|
+
dict = JCON::Parser.parse('type T = string;;')
|
69
|
+
dict.definitions.length.should == 1
|
70
|
+
dict = JCON::Parser.parse('type T = string;type U = String;')
|
71
|
+
dict.definitions.length.should == 2
|
72
|
+
dict = JCON::Parser.parse(';;string;;')
|
73
|
+
dict.start.should be_an_instance_of(JCON::Types::SimpleType)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should ignore comments" do
|
77
|
+
JCON::Parser.parse("// comment\nstring").start.should be_an_instance_of(JCON::Types::SimpleType)
|
78
|
+
JCON::Parser.parse("string// comment\n").start.should be_an_instance_of(JCON::Types::SimpleType)
|
79
|
+
JCON::Parser.parse("string\n// comment\n").start.should be_an_instance_of(JCON::Types::SimpleType)
|
80
|
+
|
81
|
+
JCON::Parser.parse("type T = // comment\nstring").definitions.length.should == 1
|
82
|
+
JCON::Parser.parse("type T = // comment\nstring").definitions.length.should == 1
|
83
|
+
JCON::Parser.parse("type T = /* comment */ string").definitions.length.should == 1
|
84
|
+
JCON::Parser.parse("type T = /* multiline\ncomment */ string").definitions.length.should == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should report parse errors" do
|
88
|
+
lambda { JCON::Parser.parse('type') }.should raise_error("expected identifier at ''")
|
89
|
+
lambda { JCON::Parser.parse('type S') }.should raise_error("expected = at ''")
|
90
|
+
lambda { JCON::Parser.parse('[1') }.should raise_error("expected comma at ''")
|
91
|
+
lambda { JCON::Parser.parse('[1,,') }.should raise_error("unexpected token at ','")
|
92
|
+
lambda { JCON::Parser.parse('[1,]') }.should raise_error("unexpected token at ']'")
|
93
|
+
lambda { JCON::Parser.parse('[1]]') }.should raise_error("expected end of input at ']'")
|
94
|
+
lambda { JCON::Parser.parse('[1 2]') }.should raise_error("expected comma at '2]'")
|
95
|
+
lambda { JCON::Parser.parse('{a') }.should raise_error("expected : at ''")
|
96
|
+
lambda { JCON::Parser.parse('{a:') }.should raise_error("unexpected token at ''")
|
97
|
+
lambda { JCON::Parser.parse('{a:1') }.should raise_error("expected comma at ''")
|
98
|
+
lambda { JCON::Parser.parse('{a:1,') }.should raise_error("expected id at ''")
|
99
|
+
lambda { JCON::Parser.parse('{a:1}}') }.should raise_error("expected end of input at '}'")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe :start do
|
104
|
+
it "should return the first definition" do
|
105
|
+
type = JCON::Parser.parse('type T = string; type U = String;').start
|
106
|
+
type.should be_an_instance_of(JCON::Types::SimpleType)
|
107
|
+
type.name.should == :string
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: jcon
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2008-04-16 00:00:00 -04:00
|
8
|
+
summary: Test JSON values against a schema.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: steele@osteele.com
|
12
|
+
homepage: http://jcon.rubyforge.org
|
13
|
+
rubyforge_project: jcon
|
14
|
+
description: Test JSON values for conformance with ECMAScript 4.0 types.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Oliver Steele
|
31
|
+
files:
|
32
|
+
- lib/jcon/conformance.rb
|
33
|
+
- lib/jcon/dictionary.rb
|
34
|
+
- lib/jcon/matchers/jcon_matchers.rb
|
35
|
+
- lib/jcon/parser.rb
|
36
|
+
- lib/jcon/types.rb
|
37
|
+
- lib/jcon.rb
|
38
|
+
- LICENSE
|
39
|
+
- Manifest
|
40
|
+
- README
|
41
|
+
- spec/conformance_spec.rb
|
42
|
+
- spec/jcon_spec.rb
|
43
|
+
- spec/matchers_spec.rb
|
44
|
+
- spec/parser_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
- TODO
|
47
|
+
- jcon.gemspec
|
48
|
+
test_files: []
|
49
|
+
|
50
|
+
rdoc_options:
|
51
|
+
- --line-numbers
|
52
|
+
- --inline-source
|
53
|
+
- --title
|
54
|
+
- Jcon
|
55
|
+
- --main
|
56
|
+
- README
|
57
|
+
- --title
|
58
|
+
- "JCON: Test JSON values against a schema"
|
59
|
+
- --main
|
60
|
+
- README
|
61
|
+
- --exclude
|
62
|
+
- spec/.*
|
63
|
+
extra_rdoc_files:
|
64
|
+
- README
|
65
|
+
- TODO
|
66
|
+
- LICENSE
|
67
|
+
executables: []
|
68
|
+
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
dependencies: []
|
74
|
+
|