jcon 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|