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,102 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "JSON Table Schema",
4
+ "description": "JSON Schema for validating JSON Table structures",
5
+ "type": "object",
6
+ "properties": {
7
+ "fields": {
8
+ "type": "array",
9
+ "minItems": 1,
10
+ "items": {
11
+ "type": "object",
12
+ "properties": {
13
+ "name": {
14
+ "type": "string"
15
+ },
16
+ "title": {
17
+ "type": "string"
18
+ },
19
+ "description": {
20
+ "type": "string"
21
+ },
22
+ "type": {
23
+ "enum": [ "string", "number", "integer", "date", "time", "datetime", "boolean", "binary", "object", "geopoint", "geojson", "array", "any" ]
24
+ },
25
+ "format": {
26
+ "type": "string"
27
+ },
28
+ "constraints": {
29
+ "type": "object",
30
+ "properties": {
31
+ "required": {
32
+ "type": "boolean"
33
+ },
34
+ "minLength": {
35
+ "type": "integer"
36
+ },
37
+ "maxLength": {
38
+ "type": "integer"
39
+ },
40
+ "unique": {
41
+ "type": "boolean"
42
+ },
43
+ "pattern": {
44
+ "type": "string"
45
+ },
46
+ "minimum": {
47
+ "oneOf": [
48
+ {"type": "string"},
49
+ {"type": "number"}
50
+ ]
51
+ },
52
+ "maximum": {
53
+ "oneOf": [
54
+ {"type": "string"},
55
+ {"type": "number"}
56
+ ]
57
+ }
58
+ }
59
+ }
60
+ },
61
+ "required": ["name"]
62
+ }
63
+ },
64
+ "primaryKey": {
65
+ "oneOf": [
66
+ {"type": "string"},
67
+ {"type": "array"}
68
+ ]
69
+ },
70
+ "foreignKeys": {
71
+ "type": "array",
72
+ "items": {
73
+ "type": "object",
74
+ "required": ["fields", "reference"],
75
+ "properties": {
76
+ "fields": {
77
+ "oneOf": [
78
+ {"type": "string"},
79
+ {"type": "array"}
80
+ ]
81
+ },
82
+ "reference": {
83
+ "type": "object",
84
+ "required": ["resource", "fields"],
85
+ "properties": {
86
+ "resource": {
87
+ "type": "string"
88
+ },
89
+ "fields": {
90
+ "oneOf": [
91
+ {"type": "string"},
92
+ {"type": "array"}
93
+ ]
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ },
101
+ "required": ["fields"]
102
+ }
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jsontableschema/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jsontableschema"
8
+ spec.version = JsonTableSchema::VERSION
9
+ spec.authors = ["pezholio"]
10
+ spec.email = ["pezholio@gmail.com"]
11
+
12
+ spec.summary = "A Ruby library for working with JSON Table Schema"
13
+ spec.homepage = "https://github.com/theodi/jsontableschema.rb"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.11"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "pry", "~> 0.10.0"
25
+ spec.add_development_dependency "webmock", "~> 2.1.0"
26
+ spec.add_development_dependency "coveralls", "~> 0.8.13"
27
+
28
+ spec.add_dependency "json-schema", "~> 2.6.0"
29
+ spec.add_dependency "uuid", "~> 2.3.8"
30
+ spec.add_dependency "currencies", "~> 0.4.2"
31
+ spec.add_dependency "tod", "~> 2.1.0"
32
+ end
@@ -0,0 +1,41 @@
1
+ require "json"
2
+ require "json-schema"
3
+ require "uuid"
4
+ require "currencies"
5
+ require "date"
6
+ require "tod"
7
+ require "tod/core_extensions"
8
+ require "csv"
9
+
10
+ require "jsontableschema/version"
11
+ require "jsontableschema/exceptions"
12
+ require "jsontableschema/helpers"
13
+
14
+ require "jsontableschema/constraints/constraints"
15
+
16
+ require "jsontableschema/types/base"
17
+ require "jsontableschema/types/any"
18
+ require "jsontableschema/types/array"
19
+ require "jsontableschema/types/boolean"
20
+ require "jsontableschema/types/date"
21
+ require "jsontableschema/types/datetime"
22
+ require "jsontableschema/types/geojson"
23
+ require "jsontableschema/types/geopoint"
24
+ require "jsontableschema/types/integer"
25
+ require "jsontableschema/types/null"
26
+ require "jsontableschema/types/number"
27
+ require "jsontableschema/types/object"
28
+ require "jsontableschema/types/string"
29
+ require "jsontableschema/types/time"
30
+
31
+ require "jsontableschema/validate"
32
+ require "jsontableschema/model"
33
+ require "jsontableschema/data"
34
+ require "jsontableschema/schema"
35
+ require "jsontableschema/table"
36
+ require "jsontableschema/infer"
37
+
38
+ module JsonTableSchema
39
+ module Types
40
+ end
41
+ end
@@ -0,0 +1,76 @@
1
+ require "jsontableschema/constraints/required"
2
+ require "jsontableschema/constraints/min_length"
3
+ require "jsontableschema/constraints/max_length"
4
+ require "jsontableschema/constraints/minimum"
5
+ require "jsontableschema/constraints/maximum"
6
+ require "jsontableschema/constraints/enum"
7
+ require "jsontableschema/constraints/pattern"
8
+
9
+ module JsonTableSchema
10
+ class Constraints
11
+ include JsonTableSchema::Helpers
12
+
13
+ include JsonTableSchema::Constraints::Required
14
+ include JsonTableSchema::Constraints::MinLength
15
+ include JsonTableSchema::Constraints::MaxLength
16
+ include JsonTableSchema::Constraints::Minimum
17
+ include JsonTableSchema::Constraints::Maximum
18
+ include JsonTableSchema::Constraints::Enum
19
+ include JsonTableSchema::Constraints::Pattern
20
+
21
+ def initialize(field, value)
22
+ @field = field
23
+ @value = value
24
+ @constraints = @field['constraints'] || {}
25
+ end
26
+
27
+ def validate!
28
+ result = true
29
+ @constraints.each do |c|
30
+ constraint = c.first
31
+ if is_supported_type?(constraint)
32
+ result = self.send("check_#{underscore constraint}")
33
+ else
34
+ raise(JsonTableSchema::ConstraintNotSupported.new("The field type `#{@field['type']}` does not support the `#{constraint}` constraint"))
35
+ end
36
+ end
37
+ result
38
+ end
39
+
40
+ private
41
+
42
+ def underscore(value)
43
+ value.gsub(/::/, '/').
44
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
45
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
46
+ tr("-", "_").
47
+ downcase
48
+ end
49
+
50
+ def is_supported_type?(constraint)
51
+ klass = get_class_for_type(@field['type'])
52
+ Kernel.const_get(klass).supported_constraints.include?(constraint)
53
+ end
54
+
55
+ def parse_constraint(constraint)
56
+ if @value.is_a?(::Integer) && constraint.is_a?(::String)
57
+ constraint.to_i
58
+ elsif @value.is_a?(::Tod::TimeOfDay)
59
+ Tod::TimeOfDay.parse(constraint)
60
+ elsif @value.is_a?(::DateTime)
61
+ DateTime.parse(constraint)
62
+ elsif @value.is_a?(::Date) && constraint.is_a?(::String)
63
+ Date.parse(constraint)
64
+ elsif @value.is_a?(::Float) && constraint.is_a?(Array)
65
+ constraint.map { |c| Float(c) }
66
+ elsif @value.is_a?(Boolean) && constraint.is_a?(Array)
67
+ constraint.map { |c| convert_to_boolean(c) }
68
+ elsif @value.is_a?(Date) && constraint.is_a?(Array)
69
+ constraint.map { |c| Date.parse(c) }
70
+ else
71
+ constraint
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,14 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module Enum
4
+
5
+ def check_enum
6
+ if !parse_constraint(@constraints['enum']).include?(@value)
7
+ raise JsonTableSchema::ConstraintError.new("The value for the field `#{@field['name']}` must be in the enum array")
8
+ end
9
+ true
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module MaxLength
4
+
5
+ def check_max_length
6
+ return if @value.nil?
7
+ if @value.length > @constraints['maxLength'].to_i
8
+ raise JsonTableSchema::ConstraintError.new("The field `#{@field['name']}` must have a maximum length of #{@constraints['maxLength']}")
9
+ end
10
+ true
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module Maximum
4
+
5
+ def check_maximum
6
+ if @value > parse_constraint(@constraints['maximum'])
7
+ raise JsonTableSchema::ConstraintError.new("The field `#{@field['name']}` must not be more than #{@constraints['maximum']}")
8
+ end
9
+ true
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module MinLength
4
+
5
+ def check_min_length
6
+ return if @value.nil?
7
+ if @value.length < @constraints['minLength'].to_i
8
+ raise JsonTableSchema::ConstraintError.new("The field `#{@field['name']}` must have a minimum length of #{@constraints['minLength']}")
9
+ end
10
+ true
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module Minimum
4
+
5
+ def check_minimum
6
+ if @value < parse_constraint(@constraints['minimum'])
7
+ raise JsonTableSchema::ConstraintError.new("The field `#{@field['name']}` must not be less than #{@constraints['minimum']}")
8
+ end
9
+ true
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module Pattern
4
+
5
+ def check_pattern
6
+ if !@value.to_json.match /#{@constraints['pattern']}/
7
+ raise JsonTableSchema::ConstraintError.new("The value for the field `#{@field['name']}` must match the pattern")
8
+ end
9
+ true
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ module JsonTableSchema
2
+ class Constraints
3
+ module Required
4
+
5
+ def check_required
6
+ if required? && is_empty?
7
+ raise JsonTableSchema::ConstraintError.new("The field `#{@field['name']}` requires a value")
8
+ end
9
+ true
10
+ end
11
+
12
+ private
13
+
14
+ def required?
15
+ required == true && @field['type'] != 'null'
16
+ end
17
+
18
+ def is_empty?
19
+ null_values.include?(@value)
20
+ end
21
+
22
+ def required
23
+ @constraints['required'].to_s == 'true'
24
+ end
25
+
26
+ def null_values
27
+ ['null', 'none', 'nil', 'nan', '-', '']
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ module JsonTableSchema
2
+ module Data
3
+
4
+ attr_reader :errors
5
+
6
+ def convert(rows, fail_fast = true)
7
+ @errors ||= []
8
+ rows.map! do |r|
9
+ begin
10
+ convert_row(r, fail_fast)
11
+ rescue MultipleInvalid, ConversionError => e
12
+ raise e if fail_fast == true
13
+ @errors << e if e.is_a?(ConversionError)
14
+ end
15
+ end
16
+ check_for_errors
17
+ rows
18
+ end
19
+
20
+ def convert_row(row, fail_fast = true)
21
+ @errors ||= []
22
+ raise_header_error(row) if row.count != fields.count
23
+ fields.each_with_index do |field,i|
24
+ row[i] = convert_column(row[i], field, fail_fast)
25
+ end
26
+ check_for_errors
27
+ row
28
+ end
29
+
30
+ def cast(field_name, value)
31
+ convert_column(value, get_field(field_name), true)
32
+ end
33
+
34
+ private
35
+
36
+ def raise_header_error(row)
37
+ raise(JsonTableSchema::ConversionError.new("The number of items to convert (#{row.count}) does not match the number of headers in the schema (#{fields.count})"))
38
+ end
39
+
40
+ def check_for_errors
41
+ raise(JsonTableSchema::MultipleInvalid.new("There were errors parsing the data")) if @errors.count > 0
42
+ end
43
+
44
+ def convert_column(col, field, fail_fast)
45
+ klass = get_class_for_type(field['type'] || 'string')
46
+ converter = Kernel.const_get(klass).new(field)
47
+ converter.cast(col)
48
+ rescue Exception => e
49
+ if fail_fast == true
50
+ raise e
51
+ else
52
+ @errors << e
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ module JsonTableSchema
2
+ class Exception < ::Exception ; end
3
+
4
+ class SchemaException < Exception
5
+ attr_reader :message
6
+
7
+ def initialize message
8
+ @message = message
9
+ end
10
+ end
11
+
12
+ class InvalidFormat < Exception ; end
13
+ class InvalidCast < Exception ; end
14
+ class InvalidEmail < Exception ; end
15
+ class InvalidURI < Exception ; end
16
+ class InvalidUUID < Exception ; end
17
+ class InvalidObjectType < Exception ; end
18
+ class InvalidArrayType < Exception ; end
19
+ class InvalidDateType < Exception ; end
20
+ class InvalidTimeType < Exception ; end
21
+ class InvalidDateTimeType < Exception ; end
22
+ class InvalidGeoJSONType < Exception ; end
23
+ class InvalidGeoPointType < Exception ; end
24
+ class ConstraintError < Exception ; end
25
+ class ConstraintNotSupported < Exception ; end
26
+ class ConversionError < Exception ; end
27
+ class MultipleInvalid < Exception ; end
28
+ end