Skip to content

Commit 15adaca

Browse files
committed
Run JSON-LD star test suite.
1 parent 0077ddd commit 15adaca

File tree

7 files changed

+290
-4
lines changed

7 files changed

+290
-4
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ The [MultiJson](https://rubygems.org/gems/multi_json) gem is used for parsing JS
3939

4040
### JSON-LD* (RDFStar)
4141

42-
The {JSON::LD::API.toRdf} and {JSON::LD::API.fromRdf} API methods, along with the {JSON::LD::Reader} and {JSON::LD::Writer}, include provisional support for [JSON-LD*][JSON-LD*].
42+
The {JSON::LD::API.expand}, {JSON::LD::API.compact}, {JSON::LD::API.toRdf}, and {JSON::LD::API.fromRdf} API methods, along with the {JSON::LD::Reader} and {JSON::LD::Writer}, include provisional support for [JSON-LD*][JSON-LD*].
4343

4444
Internally, an `RDF::Statement` is treated as another resource, along with `RDF::URI` and `RDF::Node`, which allows an `RDF::Statement` to have a `#subject` or `#object` which is also an `RDF::Statement`.
4545

@@ -55,6 +55,19 @@ In JSON-LD, with the `rdfstar` option set, the value of `@id`, in addition to an
5555
"ex:certainty": 0.9
5656
}
5757

58+
Additionally, the `@annotation` property (or alias) may be used on a node object or value object to annotate the statement for which the associated node is the object of a triple.
59+
60+
{
61+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
62+
"@id": "bob",
63+
"foaf:age" 23,
64+
"@annotation": {
65+
"ex:certainty": 0.9
66+
}
67+
}
68+
69+
In the first case, the embedded node is not asserted, and only appears as the subject of a triple. In the second case, the triple is asserted and used as the subject in another statement which annotates it.
70+
5871
**Note: This feature is subject to change or elimination as the standards process progresses.**
5972

6073
#### Serializing a Graph containing embedded statements

lib/json/ld.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module LD
4747
DEFAULT_CONTEXT = "http://schema.org"
4848

4949
KEYWORDS = Set.new(%w(
50+
@annotation
5051
@base
5152
@container
5253
@context
@@ -116,6 +117,7 @@ def code
116117
class CollidingKeywords < JsonLdError; @code = "colliding keywords"; end
117118
class ConflictingIndexes < JsonLdError; @code = "conflicting indexes"; end
118119
class CyclicIRIMapping < JsonLdError; @code = "cyclic IRI mapping"; end
120+
class InvalidAnnotation < JsonLdError; @code = "invalid annotation"; end
119121
class InvalidBaseIRI < JsonLdError; @code = "invalid base IRI"; end
120122
class InvalidContainerMapping < JsonLdError; @code = "invalid container mapping"; end
121123
class InvalidContextEntry < JsonLdError; @code = "invalid context entry"; end
@@ -137,7 +139,7 @@ class InvalidLocalContext < JsonLdError; @code = "invalid local context"; end
137139
class InvalidNestValue < JsonLdError; @code = "invalid @nest value"; end
138140
class InvalidPrefixValue < JsonLdError; @code = "invalid @prefix value"; end
139141
class InvalidPropagateValue < JsonLdError; @code = "invalid @propagate value"; end
140-
class InvalidEmbeddedNode < JsonLdError; @code = "invalid reified node"; end
142+
class InvalidEmbeddedNode < JsonLdError; @code = "invalid embedded node"; end
141143
class InvalidRemoteContext < JsonLdError; @code = "invalid remote context"; end
142144
class InvalidReverseProperty < JsonLdError; @code = "invalid reverse property"; end
143145
class InvalidReversePropertyMap < JsonLdError; @code = "invalid reverse property map"; end

lib/json/ld/expand.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module Expand
1111
# The following constant is used to reduce object allocations
1212
CONTAINER_INDEX_ID_TYPE = Set['@index', '@id', '@type'].freeze
1313
KEY_ID = %w(@id).freeze
14-
KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction).freeze
14+
KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction @annotation).freeze
1515
KEYS_SET_LIST_INDEX = %w(@set @list @index).freeze
1616
KEYS_INCLUDED_TYPE = %w(@included @type).freeze
1717

@@ -172,6 +172,18 @@ def expand(input, active_property, context,
172172

173173
# If result contains the key @set, then set result to the key's associated value.
174174
return output_object['@set'] if output_object.key?('@set')
175+
elsif output_object['@annotation']
176+
# Otherwise, if result contains the key @annotation,
177+
# the array value must all be node objects without an @id property, otherwise, an invalid annotation error has been detected and processing is aborted.
178+
raise JsonLdError::InvalidAnnotation,
179+
"@annotation must reference node objects without @id." unless
180+
output_object['@annotation'].all? {|o| node?(o) && !o.key?('@id')}
181+
182+
# Additionally, the property must not be used if there is no active property, or the expanded active property is @graph.
183+
raise JsonLdError::InvalidAnnotation,
184+
"@annotation must not be used on a top-level object." if
185+
%w(@graph @included).include?(expanded_active_property || '@graph')
186+
175187
end
176188

177189
# If result contains only the key @language, set result to null.
@@ -258,6 +270,11 @@ def expand_object(input, active_property, context, output_object,
258270

259271
expanded_value = case expanded_property
260272
when '@id'
273+
# If expanded active property is `@annotation`, an invalid annotation error has been found and processing is aborted.
274+
raise JsonLdError::InvalidAnnotation,
275+
"an annotation must not contain a property expanding to @id" if
276+
expanded_active_property == '@annotation' && @options[:rdfstar]
277+
261278
# If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
262279
e_id = case value
263280
when String
@@ -522,6 +539,12 @@ def expand_object(input, active_property, context, output_object,
522539
nests << key
523540
# Continue with the next key from element
524541
next
542+
when '@annotation'
543+
# Skip unless rdfstar option is set
544+
next unless @options[:rdfstar]
545+
as_array(expand(value, '@annotation', context,
546+
framing: framing,
547+
log_depth: log_depth.to_i + 1))
525548
else
526549
# Skip unknown keyword
527550
next

spec/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
/uri-cache/
33
/json-ld-api
44
/json-ld-framing
5+
/json-ld-star
56
/json-ld-streaming

spec/expand_spec.rb

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3388,6 +3388,36 @@
33883388
}),
33893389
exception: JSON::LD::JsonLdError::InvalidIdValue
33903390
},
3391+
"node object with @annotation property is ignored without rdfstar option": {
3392+
input: %({
3393+
"@id": "ex:bob",
3394+
"ex:knows": {
3395+
"@id": "ex:fred",
3396+
"@annotation": {
3397+
"ex:certainty": 0.8
3398+
}
3399+
}
3400+
}),
3401+
output: %([{
3402+
"@id": "ex:bob",
3403+
"ex:knows": [{"@id": "ex:fred"}]
3404+
}])
3405+
},
3406+
"value object with @annotation property is ignored without rdfstar option": {
3407+
input: %({
3408+
"@id": "ex:bob",
3409+
"ex:age": {
3410+
"@value": 23,
3411+
"@annotation": {
3412+
"ex:certainty": 0.8
3413+
}
3414+
}
3415+
}),
3416+
output: %([{
3417+
"@id": "ex:bob",
3418+
"ex:age": [{"@value": 23}]
3419+
}])
3420+
},
33913421
}.each do |title, params|
33923422
it(title) {run_expand params}
33933423
end
@@ -3569,7 +3599,7 @@
35693599
}]
35703600
}])
35713601
},
3572-
"illegal node with embedded object having properties": {
3602+
"node with embedded object having properties": {
35733603
input: %({
35743604
"@id": "ex:subj",
35753605
"ex:value": {
@@ -3619,6 +3649,197 @@
36193649
}]
36203650
}])
36213651
},
3652+
"node with @annotation property on value object": {
3653+
input: %({
3654+
"@id": "ex:bob",
3655+
"ex:age": {
3656+
"@value": 23,
3657+
"@annotation": {"ex:certainty": 0.8}
3658+
}
3659+
}),
3660+
output: %([{
3661+
"@id": "ex:bob",
3662+
"ex:age": [{
3663+
"@value": 23,
3664+
"@annotation": [{"ex:certainty": [{"@value": 0.8}]}]
3665+
}]
3666+
}])
3667+
},
3668+
"node with @annotation property on node object": {
3669+
input: %({
3670+
"@id": "ex:bob",
3671+
"ex:name": "Bob",
3672+
"ex:knows": {
3673+
"@id": "ex:fred",
3674+
"ex:name": "Fred",
3675+
"@annotation": {"ex:certainty": 0.8}
3676+
}
3677+
}),
3678+
output: %([{
3679+
"@id": "ex:bob",
3680+
"ex:name": [{"@value": "Bob"}],
3681+
"ex:knows": [{
3682+
"@id": "ex:fred",
3683+
"ex:name": [{"@value": "Fred"}],
3684+
"@annotation": [{"ex:certainty": [{"@value": 0.8}]}]
3685+
}]
3686+
}])
3687+
},
3688+
"node with @annotation property multiple values": {
3689+
input: %({
3690+
"@id": "ex:bob",
3691+
"ex:name": "Bob",
3692+
"ex:knows": {
3693+
"@id": "ex:fred",
3694+
"ex:name": "Fred",
3695+
"@annotation": [{
3696+
"ex:certainty": 0.8
3697+
}, {
3698+
"ex:source": {"@id": "http://example.org/"}
3699+
}]
3700+
}
3701+
}),
3702+
output: %([{
3703+
"@id": "ex:bob",
3704+
"ex:name": [{"@value": "Bob"}],
3705+
"ex:knows": [{
3706+
"@id": "ex:fred",
3707+
"ex:name": [{"@value": "Fred"}],
3708+
"@annotation": [{
3709+
"ex:certainty": [{"@value": 0.8}]
3710+
}, {
3711+
"ex:source": [{"@id": "http://example.org/"}]
3712+
}]
3713+
}]
3714+
}])
3715+
},
3716+
"node with @annotation property that is on the top-level is invalid": {
3717+
input: %({
3718+
"@id": "ex:bob",
3719+
"ex:name": "Bob",
3720+
"@annotation": {"ex:prop": "value2"}
3721+
}),
3722+
exception: JSON::LD::JsonLdError::InvalidAnnotation
3723+
},
3724+
"node with @annotation property on a top-level graph node is invalid": {
3725+
input: %({
3726+
"@id": "ex:bob",
3727+
"ex:name": "Bob",
3728+
"@graph": {
3729+
"@id": "ex:fred",
3730+
"ex:name": "Fred",
3731+
"@annotation": {"ex:prop": "value2"}
3732+
}
3733+
}),
3734+
exception: JSON::LD::JsonLdError::InvalidAnnotation
3735+
},
3736+
"node with @annotation property having @id is invalid": {
3737+
input: %({
3738+
"@id": "ex:bob",
3739+
"ex:knows": {
3740+
"@id": "ex:fred",
3741+
"@annotation": {
3742+
"@id": "ex:invalid-ann-id",
3743+
"ex:prop": "value2"
3744+
}
3745+
}
3746+
}),
3747+
exception: JSON::LD::JsonLdError::InvalidAnnotation
3748+
},
3749+
"node with @annotation property with value object value is invalid": {
3750+
input: %({
3751+
"@id": "ex:bob",
3752+
"ex:knows": {
3753+
"@id": "fred",
3754+
"@annotation": "value2"
3755+
}
3756+
}),
3757+
exception: JSON::LD::JsonLdError::InvalidAnnotation
3758+
},
3759+
"node with @annotation on a list": {
3760+
input: %({
3761+
"@id": "ex:bob",
3762+
"ex:knows": {
3763+
"@list": [{"@id": "ex:fred"}],
3764+
"@annotation": "value2"
3765+
}
3766+
}),
3767+
exception: JSON::LD::JsonLdError::InvalidSetOrListObject
3768+
},
3769+
"node with @annotation on a list value": {
3770+
input: %({
3771+
"@id": "ex:bob",
3772+
"ex:knows": {
3773+
"@list": [
3774+
{
3775+
"@id": "ex:fred",
3776+
"@annotation": "value2"
3777+
}
3778+
]
3779+
}
3780+
}),
3781+
exception: JSON::LD::JsonLdError::InvalidAnnotation
3782+
},
3783+
"node with @annotation property on a top-level @included node is invalid": {
3784+
input: %({
3785+
"@id": "ex:bob",
3786+
"ex:name": "Bob",
3787+
"@included": [{
3788+
"@id": "ex:fred",
3789+
"ex:name": "Fred",
3790+
"@annotation": {"ex:prop": "value2"}
3791+
}]
3792+
}),
3793+
exception: JSON::LD::JsonLdError::InvalidAnnotation
3794+
},
3795+
"node with @annotation property on embedded subject": {
3796+
input: %({
3797+
"@id": {
3798+
"@id": "ex:rei",
3799+
"ex:prop": {"@id": "_:value"}
3800+
},
3801+
"ex:prop": {
3802+
"@value": "value2",
3803+
"@annotation": {"ex:certainty": 0.8}
3804+
}
3805+
}),
3806+
output: %([{
3807+
"@id": {
3808+
"@id": "ex:rei",
3809+
"ex:prop": [{"@id": "_:value"}]
3810+
},
3811+
"ex:prop": [{
3812+
"@value": "value2",
3813+
"@annotation": [{
3814+
"ex:certainty": [{"@value": 0.8}]
3815+
}]
3816+
}]
3817+
}])
3818+
},
3819+
"node with @annotation property on embedded object": {
3820+
input: %({
3821+
"@id": "ex:subj",
3822+
"ex:value": {
3823+
"@id": {
3824+
"@id": "ex:rei",
3825+
"ex:prop": "value"
3826+
},
3827+
"@annotation": {"ex:certainty": 0.8}
3828+
}
3829+
}),
3830+
output: %([{
3831+
"@id": "ex:subj",
3832+
"ex:value": [{
3833+
"@id": {
3834+
"@id": "ex:rei",
3835+
"ex:prop": [{"@value": "value"}]
3836+
},
3837+
"@annotation": [{
3838+
"ex:certainty": [{"@value": 0.8}]
3839+
}]
3840+
}]
3841+
}])
3842+
},
36223843
}.each do |title, params|
36233844
it(title) {run_expand params.merge(rdfstar: true)}
36243845
end

spec/rdfstar_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# coding: utf-8
2+
require_relative 'spec_helper'
3+
4+
describe JSON::LD do
5+
describe "test suite" do
6+
require_relative 'suite_helper'
7+
%w{
8+
expand
9+
compact
10+
fromRdf
11+
toRdf
12+
}.each do |partial|
13+
m = Fixtures::SuiteTest::Manifest.open("#{Fixtures::SuiteTest::STAR_SUITE}#{partial}-manifest.jsonld")
14+
describe m.name do
15+
m.entries.each do |t|
16+
specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
17+
t.options[:ordered] = false
18+
expect {t.run self}.not_to write.to(:error)
19+
end
20+
end
21+
end
22+
end
23+
end
24+
end unless ENV['CI']

spec/suite_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module File
77
"https://w3c.github.io/json-ld-api/tests/" => ::File.expand_path("../json-ld-api/tests", __FILE__) + '/',
88
"https://w3c.github.io/json-ld-framing/tests/" => ::File.expand_path("../json-ld-framing/tests", __FILE__) + '/',
99
"https://w3c.github.io/json-ld-streaming/tests/" => ::File.expand_path("../json-ld-streaming/tests", __FILE__) + '/',
10+
"https://json-ld.github.io/json-ld-star/tests/" => ::File.expand_path("../json-ld-star/tests", __FILE__) + '/',
1011
"file:" => ""
1112
}
1213

@@ -76,6 +77,7 @@ module SuiteTest
7677
SUITE = RDF::URI("https://w3c.github.io/json-ld-api/tests/")
7778
FRAME_SUITE = RDF::URI("https://w3c.github.io/json-ld-framing/tests/")
7879
STREAM_SUITE = RDF::URI("https://w3c.github.io/json-ld-streaming/tests/")
80+
STAR_SUITE = RDF::URI("https://json-ld.github.io/json-ld-star/tests/")
7981

8082
class Manifest < JSON::LD::Resource
8183
attr_accessor :manifest_url

0 commit comments

Comments
 (0)