tableschema 0.3.1 → 0.4.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 (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