json-ld-validate 0.0.2 → 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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24f7e49ba2ad97d5ec2c2746ac541e17167eea91a9c618a364e3c866e979500a
|
|
4
|
+
data.tar.gz: 31a0660a604c9713e91648086fe6945078df30cba88da859eb00a579b6fc6589
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 006dc0932bce4be731d5078afbf4d98bee88b2b63af946bbfd3e3614cc956baa09e163810b23a1cb16aecbf5aeedb6d405c82354f3e7949a02d080131c8b7c9f
|
|
7
|
+
data.tar.gz: 5ceaccc40e461010b5037ebf713f6df68f7d9a3028a3c749db7bd979a23ad9edcd434eb1f12ea9b691882b2c0a6e2c30ed2b1829dfc534baa2e5a1e4900194be
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json/ld'
|
|
4
|
+
require_relative 'xsd_type_validator'
|
|
5
|
+
|
|
3
6
|
module JsonldValidate
|
|
4
7
|
class Validator
|
|
5
|
-
|
|
8
|
+
include XsdTypeValidator
|
|
9
|
+
|
|
10
|
+
attr_reader :content, :errors
|
|
6
11
|
|
|
7
12
|
def initialize(content)
|
|
8
13
|
@content = content
|
|
@@ -10,30 +15,119 @@ module JsonldValidate
|
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def valid?
|
|
18
|
+
@errors = []
|
|
13
19
|
return false if content.nil?
|
|
14
20
|
return true if content.empty?
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
return false unless errors.empty?
|
|
18
|
-
|
|
19
|
-
validates_no_unknown_field
|
|
20
|
-
|
|
22
|
+
validate_document
|
|
21
23
|
errors.empty?
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
private
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
attr_reader :doc, :expanded_doc
|
|
29
|
+
|
|
30
|
+
def validate_document
|
|
31
|
+
if content.is_a?(Array)
|
|
32
|
+
validate_array_items
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
return if hash_missing_context? && (@errors << 'missing @context')
|
|
37
|
+
|
|
38
|
+
build_expanded_doc
|
|
39
|
+
build_doc
|
|
40
|
+
return unless errors.empty?
|
|
41
|
+
|
|
42
|
+
validate_typed_values
|
|
43
|
+
find_unknown_fields(content, doc, [])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_array_items
|
|
47
|
+
content.each_with_index do |item, i|
|
|
48
|
+
item_validator = self.class.new(item)
|
|
49
|
+
next if item_validator.valid?
|
|
50
|
+
|
|
51
|
+
if item_validator.errors.empty?
|
|
52
|
+
@errors << "#{i}: invalid document"
|
|
53
|
+
else
|
|
54
|
+
item_validator.errors.each { |e| @errors << "#{i}.#{e}" }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def hash_missing_context?
|
|
60
|
+
content.is_a?(Hash) && (!content.key?('@context') || content['@context'].nil?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_doc
|
|
64
|
+
return if expanded_doc.nil?
|
|
65
|
+
|
|
66
|
+
@doc = JSON::LD::API.compact(expanded_doc, content['@context'])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def build_expanded_doc
|
|
70
|
+
@expanded_doc = clean_expanded_doc(JSON::LD::API.expand(content))
|
|
28
71
|
rescue JSON::LD::JsonLdError::LoadingDocumentFailed
|
|
29
72
|
@errors << 'failed loading document'
|
|
73
|
+
rescue JSON::LD::JsonLdError => e
|
|
74
|
+
@errors << e.to_s
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def find_unknown_fields(original, compacted, path)
|
|
78
|
+
return unless original.is_a?(Hash)
|
|
79
|
+
return report_all_fields(original, path) unless compacted.is_a?(Hash)
|
|
80
|
+
|
|
81
|
+
report_unknown_keys(original, compacted, path)
|
|
82
|
+
recurse_known_keys(original, compacted, path)
|
|
30
83
|
end
|
|
31
84
|
|
|
32
|
-
def
|
|
33
|
-
|
|
85
|
+
def report_all_fields(original, path)
|
|
86
|
+
original.each_key do |key|
|
|
87
|
+
next if key.start_with?('@')
|
|
88
|
+
|
|
89
|
+
errors << "found unknown field: #{(path + [key]).join('.')}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def report_unknown_keys(original, compacted, path)
|
|
94
|
+
(original.keys - compacted.keys).each do |field|
|
|
95
|
+
errors << "found unknown field: #{(path + [field]).join('.')}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def recurse_known_keys(original, compacted, path)
|
|
100
|
+
(original.keys & compacted.keys).each do |key|
|
|
101
|
+
next if key.start_with?('@')
|
|
102
|
+
|
|
103
|
+
recurse_field(original[key], compacted[key], path + [key])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def recurse_field(orig_val, comp_val, path)
|
|
108
|
+
if orig_val.is_a?(Array)
|
|
109
|
+
comp_val = [] unless comp_val.is_a?(Array)
|
|
110
|
+
orig_val.each_with_index do |item, i|
|
|
111
|
+
find_unknown_fields(item, comp_val[i], path + [i.to_s])
|
|
112
|
+
end
|
|
113
|
+
elsif orig_val.is_a?(Hash)
|
|
114
|
+
find_unknown_fields(orig_val, comp_val, path)
|
|
115
|
+
elsif orig_val != comp_val
|
|
116
|
+
@errors << "invalid value at #{path.join('.')}: #{orig_val.inspect}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
34
119
|
|
|
35
|
-
|
|
36
|
-
|
|
120
|
+
def clean_expanded_doc(obj)
|
|
121
|
+
case obj
|
|
122
|
+
when Array
|
|
123
|
+
obj.map { |item| clean_expanded_doc(item) }
|
|
124
|
+
when Hash
|
|
125
|
+
obj.each_with_object({}) do |(k, v), h|
|
|
126
|
+
h[k] = clean_expanded_doc(v) unless k.start_with?('_:')
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
obj
|
|
130
|
+
end
|
|
37
131
|
end
|
|
38
132
|
end
|
|
39
133
|
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonldValidate
|
|
4
|
+
module XsdTypeValidator
|
|
5
|
+
XSD = 'http://www.w3.org/2001/XMLSchema#'
|
|
6
|
+
|
|
7
|
+
INTEGER_TYPES = %w[
|
|
8
|
+
integer int long short byte
|
|
9
|
+
unsignedInt unsignedLong unsignedShort unsignedByte
|
|
10
|
+
nonNegativeInteger positiveInteger nonPositiveInteger negativeInteger
|
|
11
|
+
].to_set { |t| "#{XSD}#{t}" }.freeze
|
|
12
|
+
|
|
13
|
+
DECIMAL_TYPES = %w[decimal double float].to_set { |t| "#{XSD}#{t}" }.freeze
|
|
14
|
+
|
|
15
|
+
BOOLEAN_TYPE = "#{XSD}boolean".freeze
|
|
16
|
+
|
|
17
|
+
def validate_typed_values
|
|
18
|
+
return unless expanded_doc
|
|
19
|
+
|
|
20
|
+
iri_to_term, vocab = build_iri_map
|
|
21
|
+
expanded_doc.each { |node| check_node_types(node, iri_to_term, vocab, []) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_iri_map
|
|
25
|
+
ctx = JSON::LD::Context.parse(content['@context'])
|
|
26
|
+
map = ctx.term_definitions.each_with_object({}) do |(term, defn), h|
|
|
27
|
+
h[defn.id] = term if defn.id
|
|
28
|
+
end
|
|
29
|
+
[map, ctx.vocab]
|
|
30
|
+
rescue StandardError
|
|
31
|
+
[{}, nil]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def check_node_types(node, iri_to_term, vocab, path)
|
|
35
|
+
return unless node.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
node.each do |iri, values|
|
|
38
|
+
next if iri.start_with?('@')
|
|
39
|
+
|
|
40
|
+
term = compact_term(iri, iri_to_term, vocab)
|
|
41
|
+
Array(values).each_with_index do |value, i|
|
|
42
|
+
child_path = Array(values).length > 1 ? path + [term, i.to_s] : path + [term]
|
|
43
|
+
check_typed_value(value, child_path, iri_to_term, vocab)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_typed_value(value, path, iri_to_term, vocab)
|
|
49
|
+
return unless value.is_a?(Hash)
|
|
50
|
+
|
|
51
|
+
if value.key?('@value') && value.key?('@type')
|
|
52
|
+
report_type_mismatch(value['@value'], value['@type'], path)
|
|
53
|
+
else
|
|
54
|
+
check_node_types(value, iri_to_term, vocab, path)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def report_type_mismatch(val, type_iri, path)
|
|
59
|
+
return if valid_xsd_value?(val, type_iri)
|
|
60
|
+
|
|
61
|
+
@errors << "invalid type for #{path.join('.')}: #{val.inspect}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def valid_xsd_value?(val, type_iri)
|
|
65
|
+
if INTEGER_TYPES.include?(type_iri)
|
|
66
|
+
val.is_a?(Integer)
|
|
67
|
+
elsif DECIMAL_TYPES.include?(type_iri)
|
|
68
|
+
val.is_a?(Numeric)
|
|
69
|
+
elsif type_iri == BOOLEAN_TYPE
|
|
70
|
+
[true, false].include?(val)
|
|
71
|
+
else
|
|
72
|
+
true
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def compact_term(iri, iri_to_term, vocab)
|
|
77
|
+
return iri_to_term[iri] if iri_to_term.key?(iri)
|
|
78
|
+
return iri.delete_prefix(vocab) if vocab && iri.start_with?(vocab)
|
|
79
|
+
|
|
80
|
+
iri
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/jsonld_validate.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json-ld-validate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien MATHIEU
|
|
@@ -23,20 +23,6 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '3.0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rspec-core
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '3.0'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '3.0'
|
|
40
26
|
description: JSON-LD documents validation
|
|
41
27
|
email:
|
|
42
28
|
- 42@dmathieu.com
|
|
@@ -51,6 +37,7 @@ files:
|
|
|
51
37
|
- lib/jsonld_validate/matchers/be_valid_json_ld.rb
|
|
52
38
|
- lib/jsonld_validate/validator.rb
|
|
53
39
|
- lib/jsonld_validate/version.rb
|
|
40
|
+
- lib/jsonld_validate/xsd_type_validator.rb
|
|
54
41
|
homepage: https://codeberg.org/dmathieu/json-ld-validate
|
|
55
42
|
licenses:
|
|
56
43
|
- AGPL
|
|
@@ -70,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
57
|
- !ruby/object:Gem::Version
|
|
71
58
|
version: '0'
|
|
72
59
|
requirements: []
|
|
73
|
-
rubygems_version: 4.0.
|
|
60
|
+
rubygems_version: 4.0.6
|
|
74
61
|
specification_version: 4
|
|
75
62
|
summary: JSON-LD documents validation
|
|
76
63
|
test_files: []
|