Skip to content

Commit 1faa478

Browse files
committed
Add processingMode and @version anouncement requirement from json-ld/json-ld.org#446.
1 parent 7b93f3e commit 1faa478

File tree

7 files changed

+205
-34
lines changed

7 files changed

+205
-34
lines changed

lib/json/ld.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ module LD
6060
@set
6161
@type
6262
@value
63+
@version
6364
@vocab
6465
).freeze
6566

@@ -105,6 +106,7 @@ class InvalidContainerMapping < JsonLdError; @code = "invalid container mapping"
105106
class InvalidDefaultLanguage < JsonLdError; @code = "invalid default language"; end
106107
class InvalidIdValue < JsonLdError; @code = "invalid @id value"; end
107108
class InvalidIndexValue < JsonLdError; @code = "invalid @index value"; end
109+
class InvalidVersionValue < JsonLdError; @code = "invalid @version value"; end
108110
class InvalidIRIMapping < JsonLdError; @code = "invalid IRI mapping"; end
109111
class InvalidKeywordAlias < JsonLdError; @code = "invalid keyword alias"; end
110112
class InvalidLanguageMapping < JsonLdError; @code = "invalid language mapping"; end
@@ -132,6 +134,7 @@ class ListOfLists < JsonLdError; @code = "list of lists"; end
132134
class LoadingDocumentFailed < JsonLdError; @code = "loading document failed"; end
133135
class LoadingRemoteContextFailed < JsonLdError; @code = "loading remote context failed"; end
134136
class MultipleContextLinkHeaders < JsonLdError; @code = "multiple context link headers"; end
137+
class ProcessingModeConflict < JsonLdError; @code = "processing mode conflict"; end
135138
class RecursiveContextInclusion < JsonLdError; @code = "recursive context inclusion"; end
136139
class InvalidFrame < JsonLdError
137140
class MultipleEmbeds < InvalidFrame; end

lib/json/ld/api.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,12 @@ class API
6969
# A context that is used to initialize the active context when expanding a document.
7070
# @option options [Boolean, String, RDF::URI] :flatten
7171
# If set to a value that is not `false`, the JSON-LD processor must modify the output of the Compaction Algorithm or the Expansion Algorithm by coalescing all properties associated with each subject via the Flattening Algorithm. The value of `flatten must` be either an _IRI_ value representing the name of the graph to flatten, or `true`. If the value is `true`, then the first graph encountered in the input document is selected and flattened.
72-
# @option options [String] :processingMode ("json-ld-1.1")
72+
# @option options [String] :processingMode
7373
# Processing mode, json-ld-1.0 or json-ld-1.1. Also can have other values:
7474
#
7575
# * json-ld-1.1-expand-frame – special frame expansion mode.
76+
#
77+
# If `processingMode` is not specified, a mode of `json-ld-1.0` or `json-ld-1.1` is set, the context used for `expansion` or `compaction`.
7678
# @option options [Boolean] :rename_bnodes (true)
7779
# Rename bnodes as part of expansion, or keep them the same.
7880
# @option options [Boolean] :unique_bnodes (false)
@@ -88,10 +90,8 @@ def initialize(input, context, options = {}, &block)
8890
@options = {
8991
compactArrays: true,
9092
rename_bnodes: true,
91-
processingMode: "json-ld-1.1",
9293
documentLoader: self.class.method(:documentLoader)
9394
}
94-
@options[:validate] = %w(json-ld-1.0 json-ld-1.1).include?(@options[:processingMode])
9595
@options = @options.merge(options)
9696
@namer = options[:unique_bnodes] ? BlankNodeUniqer.new : (@options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
9797

@@ -135,6 +135,10 @@ def initialize(input, context, options = {}, &block)
135135
context ||= (@value['@context'] if @value.is_a?(Hash)) || context_ref
136136
@context = Context.parse(context || {}, @options)
137137

138+
# If not set explicitly, the context figures out the processing mode
139+
@options[:processingMode] ||= @context.processingMode
140+
@options[:validate] ||= %w(json-ld-1.0 json-ld-1.1).include?(@options[:processingMode])
141+
138142
if block_given?
139143
case block.arity
140144
when 0, -1 then instance_eval(&block)

lib/json/ld/context.rb

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ def inspect
205205
# @return [BlankNodeNamer]
206206
attr_accessor :namer
207207

208+
# @return [String]
209+
attr_accessor :processingMode
210+
208211
##
209212
# Create a new context by parsing a context.
210213
#
@@ -242,6 +245,7 @@ def initialize(**options)
242245
@doc_base.canonicalize! if options[:canonicalize]
243246
end
244247
options[:documentLoader] ||= JSON::LD::API.method(:documentLoader)
248+
@processingMode ||= options[:processingMode]
245249
@term_definitions = {}
246250
@iri_to_term = {
247251
RDF.to_uri.to_s => "rdf",
@@ -302,6 +306,22 @@ def default_language=(value)
302306
end
303307
end
304308

309+
# If contex has a @version member, it's value MUST be 1.1, otherwise an "invalid @version value" has been detected, and processing is aborted.
310+
# If processingMode has been set, and "json-ld-1.1" is not a prefix of processingMode , a "processing mode conflict" has been detecting, and processing is aborted.
311+
# @param [Number] vaule must be a decimal number
312+
def version=(value)
313+
case value
314+
when 1.1
315+
if processingMode && !processingMode.start_with?("json-ld-1.1")
316+
raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{processingMode}"
317+
end
318+
@processingMode ||= "json-ld-1.1"
319+
else
320+
raise JsonLdError::InvalidVersionValue, value
321+
end
322+
end
323+
324+
# If context has a @vocab member: if its value is not a valid absolute IRI or null trigger an INVALID_VOCAB_MAPPING error; otherwise set the active context's vocabulary mapping to its value and remove the @vocab member from context.
305325
# @param [String] value must be an absolute IRI
306326
def vocab=(value)
307327
@vocab = case value
@@ -399,7 +419,7 @@ def parse(local_context, remote_contexts = [])
399419
end
400420
raise JsonLdError::InvalidRemoteContext, "#{context}" unless jo.is_a?(Hash) && jo.has_key?('@context')
401421
context = jo['@context']
402-
if @options[:processingMode] == "json-ld-1.0"
422+
if processingMode && processingMode <= "json-ld-1.1"
403423
context_no_base.provided_context = context.dup
404424
end
405425
end
@@ -421,11 +441,12 @@ def parse(local_context, remote_contexts = [])
421441
result = context
422442
#log_debug("parse") {"=> provided_context: #{context.inspect}"}
423443
when Hash
424-
# If context has a @vocab member: if its value is not a valid absolute IRI or null trigger an INVALID_VOCAB_MAPPING error; otherwise set the active context's vocabulary mapping to its value and remove the @vocab member from context.
425444
context = context.dup # keep from modifying a hash passed as a param
445+
426446
{
427447
'@base' => :base=,
428448
'@language' => :default_language=,
449+
'@version' => :version=,
429450
'@vocab' => :vocab=,
430451
}.each do |key, setter|
431452
v = context.fetch(key, false)
@@ -436,6 +457,9 @@ def parse(local_context, remote_contexts = [])
436457
end
437458
end
438459

460+
# If not set explicitly, set processingMode to "json-ld-1.0"
461+
result.processingMode ||= "json-ld-1.0"
462+
439463
defined = {}
440464
# For each key-value pair in context invoke the Create Term Definition subalgorithm, passing result for active context, context for local context, key, and defined
441465
context.each_key do |key|
@@ -504,7 +528,7 @@ def create_term_definition(local_context, term, defined)
504528
end
505529

506530
# Since keywords cannot be overridden, term must not be a keyword. Otherwise, an invalid value has been detected, which is an error.
507-
if KEYWORDS.include?(term) && !%w(@vocab @language).include?(term)
531+
if KEYWORDS.include?(term) && !%w(@vocab @language @version).include?(term)
508532
raise JsonLdError::KeywordRedefinition, "term must not be a keyword: #{term.inspect}" if
509533
@options[:validate]
510534
elsif !term_valid?(term) && @options[:validate]
@@ -581,11 +605,10 @@ def create_term_definition(local_context, term, defined)
581605
# If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, @index, @type, @id, an absolute IRI nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
582606
if value.has_key?('@container')
583607
container = value['@container']
584-
# FIXME: Are URIS, @id, and @type reasonable for reverse mappings?
585608
raise JsonLdError::InvalidReverseProperty,
586-
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
587-
%w(@language @list).include?(container)
588-
definition.container_mapping = check_container(container, local_context, defined, term)
609+
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
610+
['@set', '@index'].include?(container)
611+
definition.container_mapping = check_container(container, local_context, defined, term)
589612
end
590613
definition.reverse_property = true
591614
elsif value.has_key?('@id') && value['@id'] != term
@@ -626,9 +649,8 @@ def create_term_definition(local_context, term, defined)
626649
end
627650

628651
if value.has_key?('@context')
629-
context = value['@context']
630652
# Not supported in JSON-LD 1.0
631-
raise JsonLdError::InvalidScopedContext, '@context not valid in term definition' if @options[:processingMode] < 'json-ld-1.1'
653+
raise JsonLdError::InvalidTermDefinition, '@context not valid in term definition for JSON-LD 1.0' if processingMode < 'json-ld-1.1'
632654

633655
begin
634656
self.parse(value['@context'])
@@ -647,6 +669,12 @@ def create_term_definition(local_context, term, defined)
647669
end
648670

649671
if value.has_key?('@nest')
672+
# Not supported in JSON-LD 1.0
673+
raise JsonLdError::InvalidTermDefinition, '@nest not valid in term definition for JSON-LD 1.0' if processingMode < 'json-ld-1.1'
674+
675+
# Not supported in JSON-LD 1.0
676+
raise JsonLdError::InvalidTermDefinition, '@nest not valid in term definition for JSON-LD 1.0' if processingMode < 'json-ld-1.1'
677+
650678
nest = value['@nest']
651679
raise JsonLdError::InvalidNestValue, "nest must be a string, was #{nest.inspect}} on term #{term.inspect}" unless nest.is_a?(String)
652680
raise JsonLdError::InvalidNestValue, "nest must not be a keyword other than @nest, was #{nest.inspect}} on term #{term.inspect}" if nest.start_with?('@') && nest != '@nest'
@@ -1310,6 +1338,7 @@ def inspect
13101338
v = %w([Context)
13111339
v << "base=#{base}" if base
13121340
v << "vocab=#{vocab}" if vocab
1341+
v << "processingMode=#{processingMode}" if processingMode
13131342
v << "default_language=#{default_language}" if default_language
13141343
v << "term_definitions[#{term_definitions.length}]=#{term_definitions}"
13151344
v.join(" ") + "]"
@@ -1538,7 +1567,7 @@ def check_container(container, local_context, defined, term)
15381567
when '@type', '@id', nil
15391568
raise JsonLdError::InvalidContainerMapping,
15401569
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
1541-
@options[:processingMode] < 'json-ld-1.1'
1570+
processingMode < 'json-ld-1.1'
15421571
else
15431572
raise JsonLdError::InvalidContainerMapping,
15441573
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"

spec/compact_spec.rb

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@
572572
})
573573
},
574574
}.each_pair do |title, params|
575-
it(title) {run_compact(params)}
575+
it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
576576
end
577577
end
578578

@@ -677,7 +677,7 @@
677677
})
678678
},
679679
}.each_pair do |title, params|
680-
it(title) {run_compact(params)}
680+
it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
681681
end
682682
end
683683

@@ -926,9 +926,9 @@
926926
"term": {"@id": "http://example/foo", "@nest": "unknown"}
927927
}),
928928
exception: JSON::LD::JsonLdError::InvalidNestValue
929-
}
929+
},
930930
}.each_pair do |title, params|
931-
it(title) {run_compact(params)}
931+
it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
932932
end
933933
end
934934

@@ -1075,8 +1075,19 @@
10751075
"c": "C in example"
10761076
}),
10771077
},
1078+
"Raises InvalidTermDefinition if processingMode is not specified" => {
1079+
input: %([{
1080+
"http://example/foo": [{"http://example.org/bar": [{"@value": "baz"}]}]
1081+
}]),
1082+
context: %({
1083+
"@vocab": "http://example/",
1084+
"foo": {"@context": {"bar": "http://example.org/bar"}}
1085+
}),
1086+
processingMode: nil,
1087+
exception: JSON::LD::JsonLdError::InvalidTermDefinition
1088+
},
10781089
}.each_pair do |title, params|
1079-
it(title) {run_compact(params)}
1090+
it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
10801091
end
10811092
end
10821093

@@ -1219,8 +1230,24 @@
12191230
}
12201231
})
12211232
},
1233+
"Raises InvalidTermDefinition if processingMode is not specified" => {
1234+
input: %([
1235+
{
1236+
"http://example/a": [{
1237+
"@type": ["http://example/Foo"],
1238+
"http://example.org/bar": [{"@value": "baz"}]
1239+
}]
1240+
}
1241+
]),
1242+
context: %({
1243+
"@vocab": "http://example/",
1244+
"Foo": {"@context": {"bar": "http://example.org/bar"}}
1245+
}),
1246+
processingMode: nil,
1247+
exception: JSON::LD::JsonLdError::InvalidTermDefinition
1248+
},
12221249
}.each_pair do |title, params|
1223-
it(title) {run_compact(params)}
1250+
it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
12241251
end
12251252
end
12261253

@@ -1248,15 +1275,15 @@
12481275
end
12491276

12501277
def run_compact(params)
1251-
input, output, context = params[:input], params[:output], params[:context]
1278+
input, output, context, processingMode = params[:input], params[:output], params[:context], params[:processingMode]
12521279
input = ::JSON.parse(input) if input.is_a?(String)
12531280
output = ::JSON.parse(output) if output.is_a?(String)
12541281
context = ::JSON.parse(context) if context.is_a?(String)
12551282
pending params.fetch(:pending, "test implementation") unless input
12561283
if params[:exception]
1257-
expect {JSON::LD::API.compact(input, context, logger: logger)}.to raise_error(params[:exception])
1284+
expect {JSON::LD::API.compact(input, context, logger: logger, processingMode: processingMode)}.to raise_error(params[:exception])
12581285
else
1259-
jld = JSON::LD::API.compact(input, context, logger: logger)
1286+
jld = JSON::LD::API.compact(input, context, logger: logger, processingMode: processingMode)
12601287
expect(jld).to produce(output, logger)
12611288
end
12621289
end

spec/context_spec.rb

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def containers
112112
let(:ctx) {"http://example.com/preloaded"}
113113
before(:all) {
114114
JSON::LD::Context.add_preloaded("http://example.com/preloaded",
115-
JSON::LD::Context.new().parse({'foo' => "http://example.com/"})
115+
JSON::LD::Context.parse({'foo' => "http://example.com/"})
116116
)}
117117
after(:all) {JSON::LD::Context::PRELOADED.clear}
118118

@@ -362,9 +362,50 @@ def containers
362362
end
363363
end
364364

365+
describe "#processingMode" do
366+
it "sets to json-ld-1.0 if not specified" do
367+
[
368+
%({}),
369+
%([{}]),
370+
].each do |str|
371+
ctx = JSON::LD::Context.parse(::JSON.parse(str))
372+
expect(ctx.processingMode).to eql "json-ld-1.0"
373+
end
374+
end
375+
376+
it "sets to json-ld-1.1 if @version: 1.1" do
377+
[
378+
%({"@version": 1.1}),
379+
%([{"@version": 1.1}]),
380+
].each do |str|
381+
ctx = JSON::LD::Context.parse(::JSON.parse(str))
382+
expect(ctx.processingMode).to eql "json-ld-1.1"
383+
end
384+
end
385+
386+
it "raises InvalidVersionValue if @version out of scope" do
387+
[
388+
"1.1",
389+
"1.0",
390+
1.0,
391+
"foo"
392+
].each do |vers|
393+
expect {JSON::LD::Context.parse({"@version" => vers})}.to raise_error(JSON::LD::JsonLdError::InvalidVersionValue)
394+
end
395+
end
396+
397+
it "raises ProcessingModeConflict if provided processing mode conflicts with context" do
398+
expect {JSON::LD::Context.parse({"@version" => 1.1}, processingMode: "json-ld-1.0")}.to raise_error(JSON::LD::JsonLdError::ProcessingModeConflict)
399+
end
400+
401+
it "raises ProcessingModeConflict nested context is different from starting context" do
402+
expect {JSON::LD::Context.parse([{}, {"@version" => 1.1}])}.to raise_error(JSON::LD::JsonLdError::ProcessingModeConflict)
403+
end
404+
end
405+
365406
describe "#merge" do
366407
it "creates a new context with components of each" do
367-
c2 = JSON::LD::Context.new().parse({'foo' => "http://example.com/"})
408+
c2 = JSON::LD::Context.parse({'foo' => "http://example.com/"})
368409
cm = context.merge(c2)
369410
expect(cm).not_to equal context
370411
expect(cm).not_to equal c2
@@ -374,7 +415,7 @@ def containers
374415

375416
describe "#merge!" do
376417
it "updates context with components from new" do
377-
c2 = JSON::LD::Context.new().parse({'foo' => "http://example.com/"})
418+
c2 = JSON::LD::Context.parse({'foo' => "http://example.com/"})
378419
cm = context.merge!(c2)
379420
expect(cm).to equal context
380421
expect(cm).not_to equal c2

0 commit comments

Comments
 (0)