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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +17 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +233 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/etc/schemas/geojson.json +209 -0
- data/etc/schemas/json-table-schema.json +102 -0
- data/jsontableschema.gemspec +32 -0
- data/lib/jsontableschema.rb +41 -0
- data/lib/jsontableschema/constraints/constraints.rb +76 -0
- data/lib/jsontableschema/constraints/enum.rb +14 -0
- data/lib/jsontableschema/constraints/max_length.rb +15 -0
- data/lib/jsontableschema/constraints/maximum.rb +14 -0
- data/lib/jsontableschema/constraints/min_length.rb +15 -0
- data/lib/jsontableschema/constraints/minimum.rb +14 -0
- data/lib/jsontableschema/constraints/pattern.rb +14 -0
- data/lib/jsontableschema/constraints/required.rb +32 -0
- data/lib/jsontableschema/data.rb +57 -0
- data/lib/jsontableschema/exceptions.rb +28 -0
- data/lib/jsontableschema/helpers.rb +48 -0
- data/lib/jsontableschema/infer.rb +142 -0
- data/lib/jsontableschema/model.rb +73 -0
- data/lib/jsontableschema/schema.rb +35 -0
- data/lib/jsontableschema/table.rb +50 -0
- data/lib/jsontableschema/types/any.rb +23 -0
- data/lib/jsontableschema/types/array.rb +37 -0
- data/lib/jsontableschema/types/base.rb +54 -0
- data/lib/jsontableschema/types/boolean.rb +35 -0
- data/lib/jsontableschema/types/date.rb +56 -0
- data/lib/jsontableschema/types/datetime.rb +63 -0
- data/lib/jsontableschema/types/geojson.rb +38 -0
- data/lib/jsontableschema/types/geopoint.rb +56 -0
- data/lib/jsontableschema/types/integer.rb +35 -0
- data/lib/jsontableschema/types/null.rb +37 -0
- data/lib/jsontableschema/types/number.rb +60 -0
- data/lib/jsontableschema/types/object.rb +37 -0
- data/lib/jsontableschema/types/string.rb +64 -0
- data/lib/jsontableschema/types/time.rb +55 -0
- data/lib/jsontableschema/validate.rb +54 -0
- data/lib/jsontableschema/version.rb +3 -0
- 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
|