jsontableschema 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +17 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +233 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/etc/schemas/geojson.json +209 -0
- data/etc/schemas/json-table-schema.json +102 -0
- data/jsontableschema.gemspec +32 -0
- data/lib/jsontableschema.rb +41 -0
- data/lib/jsontableschema/constraints/constraints.rb +76 -0
- data/lib/jsontableschema/constraints/enum.rb +14 -0
- data/lib/jsontableschema/constraints/max_length.rb +15 -0
- data/lib/jsontableschema/constraints/maximum.rb +14 -0
- data/lib/jsontableschema/constraints/min_length.rb +15 -0
- data/lib/jsontableschema/constraints/minimum.rb +14 -0
- data/lib/jsontableschema/constraints/pattern.rb +14 -0
- data/lib/jsontableschema/constraints/required.rb +32 -0
- data/lib/jsontableschema/data.rb +57 -0
- data/lib/jsontableschema/exceptions.rb +28 -0
- data/lib/jsontableschema/helpers.rb +48 -0
- data/lib/jsontableschema/infer.rb +142 -0
- data/lib/jsontableschema/model.rb +73 -0
- data/lib/jsontableschema/schema.rb +35 -0
- data/lib/jsontableschema/table.rb +50 -0
- data/lib/jsontableschema/types/any.rb +23 -0
- data/lib/jsontableschema/types/array.rb +37 -0
- data/lib/jsontableschema/types/base.rb +54 -0
- data/lib/jsontableschema/types/boolean.rb +35 -0
- data/lib/jsontableschema/types/date.rb +56 -0
- data/lib/jsontableschema/types/datetime.rb +63 -0
- data/lib/jsontableschema/types/geojson.rb +38 -0
- data/lib/jsontableschema/types/geopoint.rb +56 -0
- data/lib/jsontableschema/types/integer.rb +35 -0
- data/lib/jsontableschema/types/null.rb +37 -0
- data/lib/jsontableschema/types/number.rb +60 -0
- data/lib/jsontableschema/types/object.rb +37 -0
- data/lib/jsontableschema/types/string.rb +64 -0
- data/lib/jsontableschema/types/time.rb +55 -0
- data/lib/jsontableschema/validate.rb +54 -0
- data/lib/jsontableschema/version.rb +3 -0
- metadata +230 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
# Hack to check against one type from http://stackoverflow.com/a/3028378/452684
|
2
|
+
# because Ruby doesn't have a single boolean class
|
3
|
+
module Boolean; end
|
4
|
+
class TrueClass; include Boolean; end
|
5
|
+
class FalseClass; include Boolean; end
|
6
|
+
|
7
|
+
module JsonTableSchema
|
8
|
+
module Types
|
9
|
+
class Boolean < Base
|
10
|
+
|
11
|
+
def name
|
12
|
+
'boolean'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.supported_constraints
|
16
|
+
[
|
17
|
+
'required',
|
18
|
+
'pattern',
|
19
|
+
'enum',
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
::Boolean
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast_default(value)
|
28
|
+
value = convert_to_boolean(value)
|
29
|
+
raise JsonTableSchema::InvalidCast.new("#{value} is not a #{name}") if value.nil?
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class Date < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'date'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
'minimum',
|
15
|
+
'maximum',
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
::Date
|
21
|
+
end
|
22
|
+
|
23
|
+
def iso8601
|
24
|
+
'%Y-%m-%d'
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast_default(value)
|
28
|
+
@format_string = iso8601
|
29
|
+
cast_fmt(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def cast_any(value)
|
33
|
+
return value if value.is_a?(type)
|
34
|
+
|
35
|
+
date = ::Date._parse(value)
|
36
|
+
if date.values.count == 3
|
37
|
+
::Date.parse(value)
|
38
|
+
else
|
39
|
+
raise JsonTableSchema::InvalidDateType.new("#{value} is not a valid date")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def cast_fmt(value)
|
44
|
+
return value if value.is_a?(type)
|
45
|
+
|
46
|
+
begin
|
47
|
+
return ::Date.strptime(value, @format_string)
|
48
|
+
rescue ArgumentError
|
49
|
+
raise JsonTableSchema::InvalidDateType.new("#{value} is not a valid date")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class DateTime < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'datetime'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
'minimum',
|
15
|
+
'maximum'
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
::DateTime
|
21
|
+
end
|
22
|
+
|
23
|
+
def iso8601
|
24
|
+
'%Y-%m-%dT%H:%M:%SZ'
|
25
|
+
end
|
26
|
+
|
27
|
+
# raw_formats = ['DD/MM/YYYYThh/mm/ss']
|
28
|
+
# py_formats = ['%Y/%m/%dT%H:%M:%S']
|
29
|
+
# format_map = dict(zip(raw_formats, py_formats))
|
30
|
+
|
31
|
+
def cast_default(value)
|
32
|
+
@format_string = iso8601
|
33
|
+
cast_fmt(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def cast_any(value)
|
37
|
+
return value if value.is_a?(type)
|
38
|
+
|
39
|
+
begin
|
40
|
+
date = ::DateTime._parse(value)
|
41
|
+
if date.values.count >= 4
|
42
|
+
::DateTime.parse(value)
|
43
|
+
else
|
44
|
+
raise JsonTableSchema::InvalidDateTimeType.new("#{value} is not a valid datetime")
|
45
|
+
end
|
46
|
+
rescue ArgumentError
|
47
|
+
raise JsonTableSchema::InvalidDateTimeType.new("#{value} is not a valid datetime")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def cast_fmt(value)
|
52
|
+
return value if value.is_a?(type)
|
53
|
+
|
54
|
+
begin
|
55
|
+
return ::DateTime.strptime(value, @format_string)
|
56
|
+
rescue ArgumentError
|
57
|
+
raise JsonTableSchema::InvalidDateTimeType.new("#{value} is not a valid date")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class GeoJSON < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'geojson'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum'
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def type
|
18
|
+
::Hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def cast_default(value)
|
22
|
+
value = JSON.parse(value) if !value.is_a?(type)
|
23
|
+
JSON::Validator.validate!(geojson_schema, value)
|
24
|
+
value
|
25
|
+
rescue JSON::Schema::ValidationError, JSON::ParserError
|
26
|
+
raise JsonTableSchema::InvalidGeoJSONType.new("#{value} is not valid GeoJSON")
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def geojson_schema
|
32
|
+
path = File.join( File.dirname(__FILE__), "..", "..", "..", "etc", "schemas", "geojson.json" )
|
33
|
+
@geojson_schema ||= JSON.parse File.read(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class GeoPoint < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'geopoint'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum'
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def types
|
18
|
+
[::String, ::Array, ::Hash]
|
19
|
+
end
|
20
|
+
|
21
|
+
def cast_default(value)
|
22
|
+
latlng = value.split(',', 2)
|
23
|
+
cast_array([latlng[0], latlng[1]])
|
24
|
+
end
|
25
|
+
|
26
|
+
def cast_object(value)
|
27
|
+
value = JSON.parse(value) if value.is_a?(::String)
|
28
|
+
cast_array([value['longitude'], value['latitude']])
|
29
|
+
rescue JSON::ParserError
|
30
|
+
raise JsonTableSchema::InvalidGeoPointType.new("#{value} is not a valid geopoint")
|
31
|
+
end
|
32
|
+
|
33
|
+
def cast_array(value)
|
34
|
+
value = JSON.parse(value) if value.is_a?(::String)
|
35
|
+
value = [Float(value[0]), Float(value[1])]
|
36
|
+
check_latlng_range(value)
|
37
|
+
value
|
38
|
+
rescue JSON::ParserError, ArgumentError, TypeError
|
39
|
+
raise JsonTableSchema::InvalidGeoPointType.new("#{value} is not a valid geopoint")
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def check_latlng_range(geopoint)
|
45
|
+
longitude = geopoint[0]
|
46
|
+
latitude = geopoint[1]
|
47
|
+
if longitude >= 180 or longitude <= -180
|
48
|
+
raise JsonTableSchema::InvalidGeoPointType.new("longtitude should be between -180 and 180, found `#{longitude}`")
|
49
|
+
elsif latitude >= 90 or latitude <= -90
|
50
|
+
raise JsonTableSchema::InvalidGeoPointType.new("longtitude should be between -90 and 90, found `#{latitude}`")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class Integer < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'integer'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
'minimum',
|
15
|
+
'maximum',
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
::Integer
|
21
|
+
end
|
22
|
+
|
23
|
+
def cast_default(value)
|
24
|
+
if value.is_a?(type)
|
25
|
+
value
|
26
|
+
else
|
27
|
+
Integer(value)
|
28
|
+
end
|
29
|
+
rescue ArgumentError
|
30
|
+
raise JsonTableSchema::InvalidCast.new("#{value} is not a #{name}")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class Null < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'null'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def type
|
18
|
+
::NilClass
|
19
|
+
end
|
20
|
+
|
21
|
+
def null_values
|
22
|
+
['null', 'none', 'nil', 'nan', '-', '']
|
23
|
+
end
|
24
|
+
|
25
|
+
def cast_default(value)
|
26
|
+
if value.is_a?(type)
|
27
|
+
return value
|
28
|
+
elsif null_values.include?(value.to_s.downcase)
|
29
|
+
nil
|
30
|
+
else
|
31
|
+
raise JsonTableSchema::InvalidCast.new("#{value} is not a #{name}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class Number < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'number'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
'minimum',
|
15
|
+
'maximum',
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
::Float
|
21
|
+
end
|
22
|
+
|
23
|
+
def currency_symbols
|
24
|
+
ISO4217::Currency.currencies.to_a.map { |c| Regexp.escape(c.last.symbol) rescue nil }.delete_if { |s| s.nil? }
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast_default(value)
|
28
|
+
return value if value.class == type
|
29
|
+
return Float(value) if value.class == ::Fixnum
|
30
|
+
|
31
|
+
value = preprocess_value(value)
|
32
|
+
return Float(value)
|
33
|
+
rescue ArgumentError
|
34
|
+
raise JsonTableSchema::InvalidCast.new("#{value} is not a #{name}")
|
35
|
+
end
|
36
|
+
|
37
|
+
def cast_currency(value)
|
38
|
+
cast_default(value)
|
39
|
+
rescue JsonTableSchema::InvalidCast
|
40
|
+
value = preprocess_value(value)
|
41
|
+
re = Regexp.new currency_symbols.join('|')
|
42
|
+
value.gsub!(re, '')
|
43
|
+
cast_default(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def preprocess_value(value)
|
49
|
+
group_char = @field.fetch('groupChar', ',')
|
50
|
+
decimal_char = @field.fetch('decimalChar', '.')
|
51
|
+
percent_char = /%|‰|‱|%|﹪|٪/
|
52
|
+
value.gsub(group_char, '')
|
53
|
+
.gsub(decimal_char, '.')
|
54
|
+
.gsub(percent_char, '')
|
55
|
+
.gsub(Regexp.new(currency_symbols.join '|'), '')
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class Object < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'object'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
'minLength',
|
15
|
+
'maxLength',
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
::Hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def cast_default(value)
|
24
|
+
return value if value.is_a?(type)
|
25
|
+
parsed = JSON.parse(value)
|
26
|
+
if parsed.is_a?(Hash)
|
27
|
+
return parsed
|
28
|
+
else
|
29
|
+
raise JsonTableSchema::InvalidObjectType.new("#{value} is not a valid object")
|
30
|
+
end
|
31
|
+
rescue
|
32
|
+
raise JsonTableSchema::InvalidObjectType.new("#{value} is not a valid object")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module JsonTableSchema
|
2
|
+
module Types
|
3
|
+
class String < Base
|
4
|
+
|
5
|
+
def name
|
6
|
+
'string'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.supported_constraints
|
10
|
+
[
|
11
|
+
'required',
|
12
|
+
'pattern',
|
13
|
+
'enum',
|
14
|
+
'minLength',
|
15
|
+
'maxLength',
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
::String
|
21
|
+
end
|
22
|
+
|
23
|
+
def email_pattern
|
24
|
+
/[^@]+@[^@]+\.[^@]+/
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast_default(value)
|
28
|
+
if value.is_a?(type)
|
29
|
+
return value
|
30
|
+
else
|
31
|
+
raise JsonTableSchema::InvalidCast.new("#{value} is not a #{name}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def cast_email(value)
|
36
|
+
value = cast_default(value)
|
37
|
+
if (value =~ email_pattern) != nil
|
38
|
+
value
|
39
|
+
else
|
40
|
+
raise JsonTableSchema::InvalidEmail.new("#{value} is not a valid email")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def cast_uri(value)
|
45
|
+
value = cast_default(value)
|
46
|
+
if (value =~ URI::regexp) != nil
|
47
|
+
value
|
48
|
+
else
|
49
|
+
raise JsonTableSchema::InvalidURI.new("#{value} is not a valid uri")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def cast_uuid(value)
|
54
|
+
value = cast_default(value)
|
55
|
+
if UUID.validate(value)
|
56
|
+
value
|
57
|
+
else
|
58
|
+
raise JsonTableSchema::InvalidUUID.new("#{value} is not a valid UUID")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|