tableschema 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -0
  3. data/.travis.yml +15 -1
  4. data/README.md +164 -129
  5. data/Rakefile +10 -1
  6. data/bin/console +2 -6
  7. data/{etc/schemas → lib/profiles}/geojson.json +0 -1
  8. data/lib/profiles/table-schema.json +1625 -0
  9. data/lib/profiles/topojson.json +311 -0
  10. data/lib/tableschema.rb +5 -3
  11. data/lib/tableschema/constraints/constraints.rb +12 -24
  12. data/lib/tableschema/constraints/enum.rb +6 -2
  13. data/lib/tableschema/constraints/max_length.rb +6 -2
  14. data/lib/tableschema/constraints/maximum.rb +12 -2
  15. data/lib/tableschema/constraints/min_length.rb +6 -2
  16. data/lib/tableschema/constraints/minimum.rb +12 -2
  17. data/lib/tableschema/constraints/pattern.rb +9 -2
  18. data/lib/tableschema/constraints/required.rb +6 -15
  19. data/lib/tableschema/constraints/unique.rb +12 -0
  20. data/lib/tableschema/defaults.rb +9 -0
  21. data/lib/tableschema/exceptions.rb +15 -2
  22. data/lib/tableschema/field.rb +39 -20
  23. data/lib/tableschema/helpers.rb +32 -15
  24. data/lib/tableschema/infer.rb +31 -28
  25. data/lib/tableschema/model.rb +57 -34
  26. data/lib/tableschema/schema.rb +40 -6
  27. data/lib/tableschema/table.rb +75 -26
  28. data/lib/tableschema/types/any.rb +1 -0
  29. data/lib/tableschema/types/array.rb +2 -1
  30. data/lib/tableschema/types/base.rb +9 -21
  31. data/lib/tableschema/types/date.rb +1 -0
  32. data/lib/tableschema/types/datetime.rb +1 -0
  33. data/lib/tableschema/types/duration.rb +31 -0
  34. data/lib/tableschema/types/geojson.rb +27 -5
  35. data/lib/tableschema/types/geopoint.rb +4 -3
  36. data/lib/tableschema/types/integer.rb +1 -0
  37. data/lib/tableschema/types/number.rb +40 -25
  38. data/lib/tableschema/types/object.rb +2 -1
  39. data/lib/tableschema/types/string.rb +8 -0
  40. data/lib/tableschema/types/time.rb +1 -0
  41. data/lib/tableschema/types/year.rb +34 -0
  42. data/lib/tableschema/types/yearmonth.rb +52 -0
  43. data/lib/tableschema/validate.rb +45 -29
  44. data/lib/tableschema/version.rb +1 -1
  45. data/tableschema.gemspec +2 -1
  46. metadata +31 -12
  47. data/etc/schemas/json-table-schema.json +0 -102
  48. data/lib/tableschema/data.rb +0 -60
  49. data/lib/tableschema/types/null.rb +0 -37
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'unique',
12
13
  'pattern',
13
14
  'enum'
14
15
  ]
@@ -19,18 +20,39 @@ module TableSchema
19
20
  end
20
21
 
21
22
  def cast_default(value)
22
- value = JSON.parse(value) if !value.is_a?(type)
23
- JSON::Validator.validate!(geojson_schema, value)
24
- value
23
+ parsed_value = parse_value(value)
24
+ JSON::Validator.validate!(geojson_schema, parsed_value)
25
+ parsed_value
25
26
  rescue JSON::Schema::ValidationError, JSON::ParserError
26
27
  raise TableSchema::InvalidGeoJSONType.new("#{value} is not valid GeoJSON")
27
28
  end
28
29
 
30
+ def cast_topojson(value)
31
+ parsed_value = parse_value(value)
32
+ JSON::Validator.validate!(topojson_schema, parsed_value)
33
+ parsed_value
34
+ rescue JSON::Schema::ValidationError, JSON::ParserError
35
+ raise TableSchema::InvalidTopoJSONType.new("#{value} is not valid TopoJSON")
36
+ end
37
+
29
38
  private
30
39
 
40
+ def parse_value(value)
41
+ if value.is_a?(type)
42
+ value
43
+ else
44
+ JSON.parse(value, symbolize_names: true)
45
+ end
46
+ end
47
+
31
48
  def geojson_schema
32
- path = File.join( File.dirname(__FILE__), "..", "..", "..", "etc", "schemas", "geojson.json" )
33
- @geojson_schema ||= JSON.parse File.read(path)
49
+ path = File.join( File.dirname(__FILE__), "..", "..", "profiles", "geojson.json" )
50
+ JSON.parse(File.read(path), symbolize_names: true)
51
+ end
52
+
53
+ def topojson_schema
54
+ path = File.join( File.dirname(__FILE__), "..", "..", "profiles", "topojson.json" )
55
+ JSON.parse(File.read(path), symbolize_names: true)
34
56
  end
35
57
 
36
58
  end
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'geopoint',
12
13
  'pattern',
13
14
  'enum'
14
15
  ]
@@ -24,14 +25,14 @@ module TableSchema
24
25
  end
25
26
 
26
27
  def cast_object(value)
27
- value = JSON.parse(value) if value.is_a?(::String)
28
- cast_array([value['longitude'], value['latitude']])
28
+ value = JSON.parse(value, symbolize_names: true) if value.is_a?(::String)
29
+ cast_array([value[:longitude], value[:latitude]])
29
30
  rescue JSON::ParserError
30
31
  raise TableSchema::InvalidGeoPointType.new("#{value} is not a valid geopoint")
31
32
  end
32
33
 
33
34
  def cast_array(value)
34
- value = JSON.parse(value) if value.is_a?(::String)
35
+ value = JSON.parse(value, symbolize_names: true) if value.is_a?(::String)
35
36
  value = [Float(value[0]), Float(value[1])]
36
37
  check_latlng_range(value)
37
38
  value
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'unique',
12
13
  'pattern',
13
14
  'enum',
14
15
  'minimum',
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'unique',
12
13
  'pattern',
13
14
  'enum',
14
15
  'minimum',
@@ -20,41 +21,55 @@ module TableSchema
20
21
  ::Float
21
22
  end
22
23
 
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
24
 
27
25
  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)
26
+ case value
27
+ when type
28
+ value
29
+ when ::Integer
30
+ Float(value)
31
+ when ::String
32
+ process_string(value)
33
+ end
33
34
  rescue ArgumentError
34
35
  raise TableSchema::InvalidCast.new("#{value} is not a #{name}")
35
36
  end
36
37
 
37
- def cast_currency(value)
38
- cast_default(value)
39
- rescue TableSchema::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
38
  private
47
39
 
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 '|'), '')
40
+ def process_string(value)
41
+ case value
42
+ when 'NaN'
43
+ Float::NAN
44
+ when '-INF'
45
+ -Float::INFINITY
46
+ when 'INF'
47
+ Float::INFINITY
48
+ else
49
+ group_char = @field.fetch(:groupChar, TableSchema::DEFAULTS[:group_char])
50
+ decimal_char = @field.fetch(:decimalChar, TableSchema::DEFAULTS[:decimal_char])
51
+ formatted_value = value.gsub(group_char, '').gsub(decimal_char, '.')
52
+ if formatted_value.match(percent_chars)
53
+ process_percent(formatted_value)
54
+ elsif @field.fetch(:currency, nil)
55
+ process_currency(formatted_value)
56
+ else
57
+ Float(formatted_value)
58
+ end
59
+ end
56
60
  end
57
61
 
62
+ def process_percent(value)
63
+ Float(value.gsub(percent_chars, '')) / 100
64
+ end
65
+
66
+ def process_currency(value)
67
+ Float(value.gsub(@field[:currency], ''))
68
+ end
69
+
70
+ def percent_chars
71
+ /%|‰|‱|%|﹪|٪/
72
+ end
58
73
  end
59
74
  end
60
75
  end
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'unique',
12
13
  'pattern',
13
14
  'enum',
14
15
  'minLength',
@@ -22,7 +23,7 @@ module TableSchema
22
23
 
23
24
  def cast_default(value)
24
25
  return value if value.is_a?(type)
25
- parsed = JSON.parse(value)
26
+ parsed = JSON.parse(value, symbolize_names: true)
26
27
  if parsed.is_a?(Hash)
27
28
  return parsed
28
29
  else
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'unique',
12
13
  'pattern',
13
14
  'enum',
14
15
  'minLength',
@@ -59,6 +60,13 @@ module TableSchema
59
60
  end
60
61
  end
61
62
 
63
+ def cast_binary(value)
64
+ value = cast_default(value)
65
+ Base64.strict_decode64(value)
66
+ rescue ArgumentError
67
+ raise TableSchema::InvalidBinary.new("#{value} is not a valid binary string")
68
+ end
69
+
62
70
  end
63
71
  end
64
72
  end
@@ -9,6 +9,7 @@ module TableSchema
9
9
  def self.supported_constraints
10
10
  [
11
11
  'required',
12
+ 'unique',
12
13
  'pattern',
13
14
  'enum',
14
15
  'minimum',
@@ -0,0 +1,34 @@
1
+ module TableSchema
2
+ module Types
3
+ class Year < Base
4
+
5
+ def name
6
+ 'year'
7
+ end
8
+
9
+ def self.supported_constraints
10
+ [
11
+ 'required',
12
+ 'unique',
13
+ 'enum',
14
+ 'minimum',
15
+ 'maximum',
16
+ ]
17
+ end
18
+
19
+ def type
20
+ ::Integer
21
+ end
22
+
23
+ def cast_default(value)
24
+ cast = ::Date._strptime(value.to_s, '%Y')
25
+ unless cast.nil? || cast.include?(:leftover)
26
+ cast[:year]
27
+ else
28
+ raise TableSchema::InvalidYearType.new("#{value} is not a valid year")
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ module TableSchema
2
+ module Types
3
+ class YearMonth < Base
4
+
5
+ def name
6
+ 'yearmonth'
7
+ end
8
+
9
+ def self.supported_constraints
10
+ [
11
+ 'required',
12
+ 'unique',
13
+ 'pattern',
14
+ 'enum',
15
+ 'minimum',
16
+ 'maximum',
17
+ ]
18
+ end
19
+
20
+ def type
21
+ ::Hash
22
+ end
23
+
24
+ def cast_default(value)
25
+ value = array_to_yearmonth_string(value) if value.class == ::Array
26
+ cast = ::Date._strptime(value, '%Y-%m')
27
+ unless cast.nil? || cast.include?(:leftover)
28
+ array_to_yearmonth(cast.values)
29
+ else
30
+ raise TableSchema::InvalidYearMonthType.new("#{value} is not a valid yearmonth")
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def array_to_yearmonth(value_array)
37
+ {
38
+ year: value_array[0],
39
+ month: value_array[1],
40
+ }.freeze
41
+ end
42
+
43
+ def array_to_yearmonth_string(value)
44
+ if value.length != 2
45
+ raise TableSchema::InvalidYearMonthType.new("#{value} is not a valid yearmonth")
46
+ end
47
+ "#{value[0]}-#{value[1]}"
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -1,54 +1,70 @@
1
1
  module TableSchema
2
2
  module Validate
3
3
 
4
- attr_reader :messages
4
+ attr_reader :errors
5
5
 
6
6
  def load_validator!
7
- filepath = File.join(File.dirname(__FILE__), '..', '..', 'etc', 'schemas', 'json-table-schema.json')
8
- @validator ||= JSON.parse(File.read filepath)
9
- end
10
-
11
- def valid?
12
- validate
13
- @messages.count == 0
7
+ filepath = File.join(File.dirname(__FILE__), '..', 'profiles', 'table-schema.json')
8
+ @profile ||= JSON.parse(File.read(filepath), symbolize_names: true)
14
9
  end
15
10
 
16
11
  def validate
17
- @messages = JSON::Validator.fully_validate(@validator, self)
12
+ @errors = Set.new(JSON::Validator.fully_validate(@profile, self))
18
13
  check_primary_keys
19
14
  check_foreign_keys
15
+ @errors.empty?
16
+ end
17
+
18
+ def validate!
19
+ validate
20
+ raise SchemaException.new(@errors.first) unless @errors.empty?
21
+ true
20
22
  end
21
23
 
22
24
  private
23
25
 
24
- def check_primary_keys
25
- return if self['primaryKey'].nil?
26
- primary_keys.each { |pk| check_field_value(pk, 'primaryKey') }
27
- end
26
+ def check_primary_keys
27
+ return if self[:primaryKey].nil?
28
+ primary_keys.each { |pk| check_field_value(pk, 'primaryKey') }
29
+ end
28
30
 
29
- def check_foreign_keys
30
- return if self['foreignKeys'].nil?
31
- self['foreignKeys'].each do |keys|
32
- foreign_key_fields(keys).each { |fk| check_field_value(fk, 'foreignKey.fields') }
33
- add_error("A JSON Table Schema foreignKey.fields must contain the same number entries as foreignKey.reference.fields.") if field_count_mismatch?(keys)
31
+ def check_foreign_keys
32
+ return if self[:foreignKeys].nil?
33
+ self[:foreignKeys].each do |key|
34
+ if field_type_mismatch?(key)
35
+ add_error("A TableSchema `foreignKey.fields` value must be the same type as `foreignKey.reference.fields`")
36
+ end
37
+ if field_count_mismatch?(key)
38
+ add_error("A TableSchema `foreignKey.fields` must contain the same number of entries as `foreignKey.reference.fields`")
39
+ end
40
+ foreign_key_fields(key).each { |fk| check_field_value(fk, 'foreignKey.fields') }
41
+ if key.fetch(:reference).fetch(:resource).empty?
42
+ foreign_key_fields(key.fetch(:reference)).each { |fk| check_field_value(fk, 'foreignKey.reference.fields')}
34
43
  end
35
44
  end
45
+ end
36
46
 
37
- def check_field_value(key, type)
38
- add_error("The JSON Table Schema #{type} value `#{key}` is not found in any of the schema's field names") if headers.select { |f| key == f }.count == 0
47
+ def check_field_value(key, type)
48
+ if headers.select { |f| key == f }.count == 0
49
+ add_error("The TableSchema #{type} value `#{key}` is not found in any of the schema's field names")
39
50
  end
51
+ end
40
52
 
41
- def foreign_key_fields(keys)
42
- [keys['fields']].flatten
43
- end
53
+ def foreign_key_fields(key)
54
+ [key.fetch(:fields)].flatten
55
+ end
44
56
 
45
- def field_count_mismatch?(keys)
46
- keys['reference'] &&([keys['fields']].flatten.count != [keys['reference']['fields']].flatten.count)
47
- end
57
+ def field_count_mismatch?(key)
58
+ foreign_key_fields(key).count != foreign_key_fields(key.fetch(:reference)).count
59
+ end
48
60
 
49
- def add_error(error)
50
- @messages << error
51
- end
61
+ def field_type_mismatch?(key)
62
+ key.fetch(:fields).class.name != key.fetch(:reference).fetch(:fields).class.name
63
+ end
64
+
65
+ def add_error(error)
66
+ @errors << error
67
+ end
52
68
 
53
69
  end
54
70
  end
@@ -1,3 +1,3 @@
1
1
  module TableSchema
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0".freeze
3
3
  end
data/tableschema.gemspec CHANGED
@@ -24,9 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "pry", "~> 0.10.0"
25
25
  spec.add_development_dependency "webmock", "~> 2.3.0"
26
26
  spec.add_development_dependency "coveralls", "~> 0.8.13"
27
+ spec.add_development_dependency "rubocop", "~> 0.49.1"
27
28
 
28
29
  spec.add_dependency "json-schema", "~> 2.6.0"
29
30
  spec.add_dependency "uuid", "~> 2.3.8"
30
- spec.add_dependency "currencies", "~> 0.4.2"
31
31
  spec.add_dependency "tod", "~> 2.1.0"
32
+ spec.add_dependency "activesupport", "~> 5.1.0"
32
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tableschema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Open Knowledge Foundation
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-21 00:00:00.000000000 Z
11
+ date: 2017-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 0.8.13
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.49.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.49.1
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: json-schema
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -123,33 +137,33 @@ dependencies:
123
137
  - !ruby/object:Gem::Version
124
138
  version: 2.3.8
125
139
  - !ruby/object:Gem::Dependency
126
- name: currencies
140
+ name: tod
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - "~>"
130
144
  - !ruby/object:Gem::Version
131
- version: 0.4.2
145
+ version: 2.1.0
132
146
  type: :runtime
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
- version: 0.4.2
152
+ version: 2.1.0
139
153
  - !ruby/object:Gem::Dependency
140
- name: tod
154
+ name: activesupport
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
157
  - - "~>"
144
158
  - !ruby/object:Gem::Version
145
- version: 2.1.0
159
+ version: 5.1.0
146
160
  type: :runtime
147
161
  prerelease: false
148
162
  version_requirements: !ruby/object:Gem::Requirement
149
163
  requirements:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
- version: 2.1.0
166
+ version: 5.1.0
153
167
  description:
154
168
  email:
155
169
  - info@okfn.org
@@ -159,6 +173,7 @@ extra_rdoc_files: []
159
173
  files:
160
174
  - ".gitignore"
161
175
  - ".rspec"
176
+ - ".rubocop.yml"
162
177
  - ".travis.yml"
163
178
  - CHANGELOG.md
164
179
  - CODE_OF_CONDUCT.md
@@ -168,8 +183,9 @@ files:
168
183
  - Rakefile
169
184
  - bin/console
170
185
  - bin/setup
171
- - etc/schemas/geojson.json
172
- - etc/schemas/json-table-schema.json
186
+ - lib/profiles/geojson.json
187
+ - lib/profiles/table-schema.json
188
+ - lib/profiles/topojson.json
173
189
  - lib/tableschema.rb
174
190
  - lib/tableschema/constraints/constraints.rb
175
191
  - lib/tableschema/constraints/enum.rb
@@ -179,7 +195,8 @@ files:
179
195
  - lib/tableschema/constraints/minimum.rb
180
196
  - lib/tableschema/constraints/pattern.rb
181
197
  - lib/tableschema/constraints/required.rb
182
- - lib/tableschema/data.rb
198
+ - lib/tableschema/constraints/unique.rb
199
+ - lib/tableschema/defaults.rb
183
200
  - lib/tableschema/exceptions.rb
184
201
  - lib/tableschema/field.rb
185
202
  - lib/tableschema/helpers.rb
@@ -193,14 +210,16 @@ files:
193
210
  - lib/tableschema/types/boolean.rb
194
211
  - lib/tableschema/types/date.rb
195
212
  - lib/tableschema/types/datetime.rb
213
+ - lib/tableschema/types/duration.rb
196
214
  - lib/tableschema/types/geojson.rb
197
215
  - lib/tableschema/types/geopoint.rb
198
216
  - lib/tableschema/types/integer.rb
199
- - lib/tableschema/types/null.rb
200
217
  - lib/tableschema/types/number.rb
201
218
  - lib/tableschema/types/object.rb
202
219
  - lib/tableschema/types/string.rb
203
220
  - lib/tableschema/types/time.rb
221
+ - lib/tableschema/types/year.rb
222
+ - lib/tableschema/types/yearmonth.rb
204
223
  - lib/tableschema/validate.rb
205
224
  - lib/tableschema/version.rb
206
225
  - tableschema.gemspec