jsontableschema 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -0
  5. data/CHANGELOG.md +17 -0
  6. data/CODE_OF_CONDUCT.md +49 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +233 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/etc/schemas/geojson.json +209 -0
  14. data/etc/schemas/json-table-schema.json +102 -0
  15. data/jsontableschema.gemspec +32 -0
  16. data/lib/jsontableschema.rb +41 -0
  17. data/lib/jsontableschema/constraints/constraints.rb +76 -0
  18. data/lib/jsontableschema/constraints/enum.rb +14 -0
  19. data/lib/jsontableschema/constraints/max_length.rb +15 -0
  20. data/lib/jsontableschema/constraints/maximum.rb +14 -0
  21. data/lib/jsontableschema/constraints/min_length.rb +15 -0
  22. data/lib/jsontableschema/constraints/minimum.rb +14 -0
  23. data/lib/jsontableschema/constraints/pattern.rb +14 -0
  24. data/lib/jsontableschema/constraints/required.rb +32 -0
  25. data/lib/jsontableschema/data.rb +57 -0
  26. data/lib/jsontableschema/exceptions.rb +28 -0
  27. data/lib/jsontableschema/helpers.rb +48 -0
  28. data/lib/jsontableschema/infer.rb +142 -0
  29. data/lib/jsontableschema/model.rb +73 -0
  30. data/lib/jsontableschema/schema.rb +35 -0
  31. data/lib/jsontableschema/table.rb +50 -0
  32. data/lib/jsontableschema/types/any.rb +23 -0
  33. data/lib/jsontableschema/types/array.rb +37 -0
  34. data/lib/jsontableschema/types/base.rb +54 -0
  35. data/lib/jsontableschema/types/boolean.rb +35 -0
  36. data/lib/jsontableschema/types/date.rb +56 -0
  37. data/lib/jsontableschema/types/datetime.rb +63 -0
  38. data/lib/jsontableschema/types/geojson.rb +38 -0
  39. data/lib/jsontableschema/types/geopoint.rb +56 -0
  40. data/lib/jsontableschema/types/integer.rb +35 -0
  41. data/lib/jsontableschema/types/null.rb +37 -0
  42. data/lib/jsontableschema/types/number.rb +60 -0
  43. data/lib/jsontableschema/types/object.rb +37 -0
  44. data/lib/jsontableschema/types/string.rb +64 -0
  45. data/lib/jsontableschema/types/time.rb +55 -0
  46. data/lib/jsontableschema/validate.rb +54 -0
  47. data/lib/jsontableschema/version.rb +3 -0
  48. 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