jsontableschema 0.1.0

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.
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