lutaml-model 0.8.3 → 0.8.5
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 +4 -4
- data/.github/workflows/dependent-tests.yml +3 -1
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +16 -22
- data/Gemfile +2 -0
- data/README.adoc +327 -3
- data/docs/_guides/document-validation.adoc +303 -0
- data/docs/_guides/index.adoc +19 -0
- data/docs/_guides/jsonld-serialization.adoc +217 -0
- data/docs/_guides/rdf-serialization.adoc +344 -0
- data/docs/_guides/turtle-serialization.adoc +224 -0
- data/docs/_guides/xml-mapping.adoc +9 -1
- data/docs/_guides/xml_mappings/07_best_practices.adoc +36 -0
- data/docs/_guides/xml_mappings/08_troubleshooting.adoc +89 -0
- data/docs/_pages/serialization_adapters.adoc +31 -0
- data/docs/_references/index.adoc +1 -0
- data/docs/_references/rdf-namespaces.adoc +243 -0
- data/docs/_tutorials/lutaml-xml-architecture.adoc +6 -1
- data/docs/index.adoc +3 -2
- data/lib/lutaml/jsonld/adapter.rb +23 -0
- data/lib/lutaml/jsonld/context.rb +69 -0
- data/lib/lutaml/jsonld/term_definition.rb +39 -0
- data/lib/lutaml/jsonld/transform.rb +174 -0
- data/lib/lutaml/jsonld.rb +23 -0
- data/lib/lutaml/model/attribute.rb +19 -1
- data/lib/lutaml/model/error/liquid_drop_already_registered_error.rb +11 -0
- data/lib/lutaml/model/error/ordered_content_mapping_error.rb +17 -0
- data/lib/lutaml/model/format_registry.rb +10 -1
- data/lib/lutaml/model/global_context.rb +1 -0
- data/lib/lutaml/model/liquefiable.rb +12 -15
- data/lib/lutaml/model/mapping/mapping_rule.rb +10 -2
- data/lib/lutaml/model/mapping_hash.rb +1 -1
- data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
- data/lib/lutaml/model/services/transformer.rb +67 -32
- data/lib/lutaml/model/transform.rb +41 -4
- data/lib/lutaml/model/uninitialized_class.rb +11 -5
- data/lib/lutaml/model/validation/concerns/has_issues.rb +27 -0
- data/lib/lutaml/model/validation/context.rb +36 -0
- data/lib/lutaml/model/validation/issue.rb +62 -0
- data/lib/lutaml/model/validation/layer_result.rb +34 -0
- data/lib/lutaml/model/validation/profile.rb +66 -0
- data/lib/lutaml/model/validation/registry.rb +60 -0
- data/lib/lutaml/model/validation/remediation.rb +33 -0
- data/lib/lutaml/model/validation/remediation_result.rb +20 -0
- data/lib/lutaml/model/validation/report.rb +39 -0
- data/lib/lutaml/model/validation/rule.rb +59 -0
- data/lib/lutaml/model/validation.rb +2 -1
- data/lib/lutaml/model/validation_framework.rb +77 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +10 -0
- data/lib/lutaml/rdf/error.rb +7 -0
- data/lib/lutaml/rdf/iri.rb +44 -0
- data/lib/lutaml/rdf/language_tagged.rb +11 -0
- data/lib/lutaml/rdf/literal.rb +62 -0
- data/lib/lutaml/rdf/mapping.rb +71 -0
- data/lib/lutaml/rdf/mapping_rule.rb +35 -0
- data/lib/lutaml/rdf/member_rule.rb +13 -0
- data/lib/lutaml/rdf/namespace.rb +58 -0
- data/lib/lutaml/rdf/namespace_set.rb +69 -0
- data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
- data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces.rb +14 -0
- data/lib/lutaml/rdf/transform.rb +36 -0
- data/lib/lutaml/rdf.rb +19 -0
- data/lib/lutaml/turtle/adapter.rb +35 -0
- data/lib/lutaml/turtle/mapping.rb +7 -0
- data/lib/lutaml/turtle/transform.rb +158 -0
- data/lib/lutaml/turtle.rb +22 -0
- data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +9 -2
- data/lib/lutaml/xml/adapter/oga_adapter.rb +11 -3
- data/lib/lutaml/xml/adapter/ox_adapter.rb +5 -2
- data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -3
- data/lib/lutaml/xml/adapter_element.rb +26 -2
- data/lib/lutaml/xml/data_model.rb +14 -0
- data/lib/lutaml/xml/document.rb +3 -0
- data/lib/lutaml/xml/element.rb +8 -2
- data/lib/lutaml/xml/mapping.rb +9 -0
- data/lib/lutaml/xml/model_transform.rb +42 -0
- data/lib/lutaml/xml/schema/xsd/base.rb +4 -1
- data/lib/lutaml/xml/serialization/instance_methods.rb +3 -1
- data/lib/lutaml/xml/transformation/ordered_applier.rb +46 -2
- data/lib/lutaml/xml/transformation.rb +40 -1
- data/lib/lutaml/xml/xml_element.rb +8 -7
- data/lutaml-model.gemspec +1 -1
- data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
- data/spec/lutaml/integration/multi_format_spec.rb +106 -0
- data/spec/lutaml/integration/round_trip_spec.rb +170 -0
- data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
- data/spec/lutaml/jsonld/context_spec.rb +114 -0
- data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
- data/spec/lutaml/jsonld/transform_spec.rb +211 -0
- data/spec/lutaml/model/attribute_default_cache_spec.rb +58 -0
- data/spec/lutaml/model/liquefiable_spec.rb +22 -6
- data/spec/lutaml/model/liquid_compatibility_spec.rb +442 -0
- data/spec/lutaml/model/ordered_content_spec.rb +5 -5
- data/spec/lutaml/model/services/transformer_spec.rb +43 -0
- data/spec/lutaml/model/transform_cache_spec.rb +62 -0
- data/spec/lutaml/model/transform_dynamic_attributes_spec.rb +41 -0
- data/spec/lutaml/model/uninitialized_class_deep_dup_spec.rb +39 -0
- data/spec/lutaml/model/uninitialized_class_spec.rb +14 -2
- data/spec/lutaml/model/validation/concerns/has_issues_spec.rb +76 -0
- data/spec/lutaml/model/validation/context_spec.rb +60 -0
- data/spec/lutaml/model/validation/issue_spec.rb +77 -0
- data/spec/lutaml/model/validation/layer_result_spec.rb +66 -0
- data/spec/lutaml/model/validation/profile_spec.rb +134 -0
- data/spec/lutaml/model/validation/registry_spec.rb +94 -0
- data/spec/lutaml/model/validation/remediation_result_spec.rb +23 -0
- data/spec/lutaml/model/validation/remediation_spec.rb +72 -0
- data/spec/lutaml/model/validation/report_spec.rb +58 -0
- data/spec/lutaml/model/validation/rule_spec.rb +134 -0
- data/spec/lutaml/model/validation/uninitialized_class_validate_spec.rb +29 -0
- data/spec/lutaml/model/validation/validation_error_spec.rb +29 -0
- data/spec/lutaml/model/validation/validation_framework_spec.rb +110 -0
- data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
- data/spec/lutaml/rdf/iri_spec.rb +73 -0
- data/spec/lutaml/rdf/literal_spec.rb +98 -0
- data/spec/lutaml/rdf/mapping_spec.rb +164 -0
- data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
- data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
- data/spec/lutaml/rdf/namespace_spec.rb +241 -0
- data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
- data/spec/lutaml/turtle/adapter_spec.rb +47 -0
- data/spec/lutaml/turtle/mapping_spec.rb +123 -0
- data/spec/lutaml/turtle/transform_spec.rb +273 -0
- data/spec/lutaml/xml/content_model_validation_spec.rb +157 -0
- data/spec/lutaml/xml/mapping_spec.rb +12 -7
- metadata +95 -7
- data/spec/fixtures/liquid_templates/_ceramics.liquid +0 -3
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "lutaml/turtle"
|
|
5
|
+
require "lutaml/jsonld"
|
|
6
|
+
|
|
7
|
+
RSpec.describe "Multi-format model" do
|
|
8
|
+
before do
|
|
9
|
+
stub_const("TestSkosNs", Class.new(Lutaml::Rdf::Namespace) do
|
|
10
|
+
uri "http://www.w3.org/2004/02/skos/core#"
|
|
11
|
+
prefix "skos"
|
|
12
|
+
end)
|
|
13
|
+
|
|
14
|
+
stub_const("MultiFormatModel", Class.new(Lutaml::Model::Serializable) do
|
|
15
|
+
attribute :name, :string
|
|
16
|
+
attribute :description, :string
|
|
17
|
+
attribute :code, :string
|
|
18
|
+
|
|
19
|
+
json do
|
|
20
|
+
map "name", to: :name
|
|
21
|
+
map "description", to: :description
|
|
22
|
+
map "code", to: :code
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
rdf do
|
|
26
|
+
namespace TestSkosNs
|
|
27
|
+
|
|
28
|
+
subject { |m| "http://example.org/concept/#{m.code}" } # rubocop:disable RSpec/NamedSubject
|
|
29
|
+
|
|
30
|
+
type "skos:Concept"
|
|
31
|
+
|
|
32
|
+
predicate :prefLabel, namespace: TestSkosNs, to: :name
|
|
33
|
+
predicate :definition, namespace: TestSkosNs, to: :description
|
|
34
|
+
predicate :notation, namespace: TestSkosNs, to: :code
|
|
35
|
+
end
|
|
36
|
+
end)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
let(:instance) do
|
|
40
|
+
MultiFormatModel.new(name: "test", description: "desc", code: "42")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "JSON format" do
|
|
44
|
+
it "serializes without @context" do
|
|
45
|
+
json = instance.to_json
|
|
46
|
+
parsed = JSON.parse(json)
|
|
47
|
+
expect(parsed).not_to have_key("@context")
|
|
48
|
+
expect(parsed["name"]).to eq("test")
|
|
49
|
+
expect(parsed["code"]).to eq("42")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "round-trips" do
|
|
53
|
+
restored = MultiFormatModel.from_json(instance.to_json)
|
|
54
|
+
expect(restored.name).to eq("test")
|
|
55
|
+
expect(restored.code).to eq("42")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "JSON-LD format" do
|
|
60
|
+
it "serializes with @type and @id" do
|
|
61
|
+
jsonld = instance.to_jsonld
|
|
62
|
+
parsed = JSON.parse(jsonld)
|
|
63
|
+
expect(parsed["@type"]).to eq("skos:Concept")
|
|
64
|
+
expect(parsed["@id"]).to eq("http://example.org/concept/42")
|
|
65
|
+
expect(parsed["prefLabel"]).to eq("test")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "round-trips" do
|
|
69
|
+
restored = MultiFormatModel.from_jsonld(instance.to_jsonld)
|
|
70
|
+
expect(restored.name).to eq("test")
|
|
71
|
+
expect(restored.code).to eq("42")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "Turtle format" do
|
|
76
|
+
it "serializes with prefixes and type" do
|
|
77
|
+
turtle = instance.to_turtle
|
|
78
|
+
expect(turtle).to include("@prefix skos:")
|
|
79
|
+
expect(turtle).to include("a skos:Concept")
|
|
80
|
+
expect(turtle).to include("<http://example.org/concept/42>")
|
|
81
|
+
expect(turtle).to include("skos:prefLabel \"test\"")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "round-trips" do
|
|
85
|
+
restored = MultiFormatModel.from_turtle(instance.to_turtle)
|
|
86
|
+
expect(restored.name).to eq("test")
|
|
87
|
+
expect(restored.code).to eq("42")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "cross-format independence" do
|
|
92
|
+
it "JSON serialization does not affect JSON-LD" do
|
|
93
|
+
json_parsed = JSON.parse(instance.to_json)
|
|
94
|
+
jsonld_parsed = JSON.parse(instance.to_jsonld)
|
|
95
|
+
expect(json_parsed).not_to have_key("@type")
|
|
96
|
+
expect(jsonld_parsed).to have_key("@type")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "JSON-LD serialization does not affect Turtle" do
|
|
100
|
+
instance.to_jsonld
|
|
101
|
+
turtle = instance.to_turtle
|
|
102
|
+
expect(turtle).not_to include("@context")
|
|
103
|
+
expect(turtle).to include("@prefix")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "lutaml/turtle"
|
|
5
|
+
require "lutaml/jsonld"
|
|
6
|
+
|
|
7
|
+
RSpec.describe "Round-trip fidelity" do
|
|
8
|
+
before do
|
|
9
|
+
stub_const("TestSkosNs", Class.new(Lutaml::Rdf::Namespace) do
|
|
10
|
+
uri "http://www.w3.org/2004/02/skos/core#"
|
|
11
|
+
prefix "skos"
|
|
12
|
+
end)
|
|
13
|
+
|
|
14
|
+
stub_const("TestExNs", Class.new(Lutaml::Rdf::Namespace) do
|
|
15
|
+
uri "http://example.org/"
|
|
16
|
+
prefix "ex"
|
|
17
|
+
end)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "Turtle round-trip" do
|
|
21
|
+
before do
|
|
22
|
+
stub_const("RtTurtleModel", Class.new(Lutaml::Model::Serializable) do
|
|
23
|
+
attribute :label, :string
|
|
24
|
+
attribute :note, :string
|
|
25
|
+
attribute :code, :integer
|
|
26
|
+
|
|
27
|
+
turtle do
|
|
28
|
+
namespace TestSkosNs
|
|
29
|
+
|
|
30
|
+
subject { |m| "http://example.org/item/#{m.code}" } # rubocop:disable RSpec/NamedSubject
|
|
31
|
+
|
|
32
|
+
type "skos:Concept"
|
|
33
|
+
predicate :prefLabel,
|
|
34
|
+
namespace: TestSkosNs,
|
|
35
|
+
to: :label
|
|
36
|
+
predicate :note,
|
|
37
|
+
namespace: TestSkosNs,
|
|
38
|
+
to: :note
|
|
39
|
+
predicate :notation,
|
|
40
|
+
namespace: TestSkosNs,
|
|
41
|
+
to: :code
|
|
42
|
+
end
|
|
43
|
+
end)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "preserves string and integer attributes" do
|
|
47
|
+
original = RtTurtleModel.new(label: "hello", note: "world", code: 99)
|
|
48
|
+
turtle = original.to_turtle
|
|
49
|
+
restored = RtTurtleModel.from_turtle(turtle)
|
|
50
|
+
expect(restored.label).to eq("hello")
|
|
51
|
+
expect(restored.note).to eq("world")
|
|
52
|
+
expect(restored.code).to eq(99)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "handles special characters in string values" do
|
|
56
|
+
original = RtTurtleModel.new(label: 'say "hi"', note: "line1\nline2",
|
|
57
|
+
code: 1)
|
|
58
|
+
turtle = original.to_turtle
|
|
59
|
+
restored = RtTurtleModel.from_turtle(turtle)
|
|
60
|
+
expect(restored.label).to eq('say "hi"')
|
|
61
|
+
expect(restored.note).to eq("line1\nline2")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "handles nil optional attributes" do
|
|
65
|
+
original = RtTurtleModel.new(label: "test", note: nil, code: 1)
|
|
66
|
+
turtle = original.to_turtle
|
|
67
|
+
restored = RtTurtleModel.from_turtle(turtle)
|
|
68
|
+
expect(restored.label).to eq("test")
|
|
69
|
+
expect(restored.note).to be_nil
|
|
70
|
+
expect(restored.code).to eq(1)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "JSON-LD round-trip" do
|
|
75
|
+
before do
|
|
76
|
+
stub_const("RtJsonLdModel", Class.new(Lutaml::Model::Serializable) do
|
|
77
|
+
attribute :title, :string
|
|
78
|
+
attribute :body, :string
|
|
79
|
+
attribute :priority, :integer
|
|
80
|
+
|
|
81
|
+
rdf do
|
|
82
|
+
namespace TestExNs
|
|
83
|
+
|
|
84
|
+
subject { |m| "http://example.org/articles/#{m.priority}" } # rubocop:disable RSpec/NamedSubject
|
|
85
|
+
|
|
86
|
+
type "http://example.org/Article"
|
|
87
|
+
|
|
88
|
+
predicate :title, namespace: TestExNs, to: :title
|
|
89
|
+
predicate :body, namespace: TestExNs, to: :body
|
|
90
|
+
predicate :priority, namespace: TestExNs, to: :priority
|
|
91
|
+
end
|
|
92
|
+
end)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "preserves all attributes through serialize → deserialize" do
|
|
96
|
+
original = RtJsonLdModel.new(title: "Test", body: "Content", priority: 5)
|
|
97
|
+
json = original.to_jsonld
|
|
98
|
+
restored = RtJsonLdModel.from_jsonld(json)
|
|
99
|
+
expect(restored.title).to eq("Test")
|
|
100
|
+
expect(restored.body).to eq("Content")
|
|
101
|
+
expect(restored.priority).to eq(5)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "preserves @context structure across round-trip" do
|
|
105
|
+
original = RtJsonLdModel.new(title: "Test", body: "Content", priority: 1)
|
|
106
|
+
json1 = original.to_jsonld
|
|
107
|
+
restored = RtJsonLdModel.from_jsonld(json1)
|
|
108
|
+
json2 = restored.to_jsonld
|
|
109
|
+
|
|
110
|
+
ctx1 = JSON.parse(json1)["@context"]
|
|
111
|
+
ctx2 = JSON.parse(json2)["@context"]
|
|
112
|
+
expect(ctx1).to eq(ctx2)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "handles nil optional attributes" do
|
|
116
|
+
original = RtJsonLdModel.new(title: "Test", body: nil, priority: 1)
|
|
117
|
+
json = original.to_jsonld
|
|
118
|
+
restored = RtJsonLdModel.from_jsonld(json)
|
|
119
|
+
expect(restored.title).to eq("Test")
|
|
120
|
+
expect(restored.body).to be_nil
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe "Error handling" do
|
|
125
|
+
it "Turtle raises MissingSubjectError without subject" do
|
|
126
|
+
stub_const("NoSubjModel", Class.new(Lutaml::Model::Serializable) do
|
|
127
|
+
attribute :name, :string
|
|
128
|
+
|
|
129
|
+
turtle do
|
|
130
|
+
namespace TestSkosNs
|
|
131
|
+
type "skos:Concept"
|
|
132
|
+
predicate :prefLabel,
|
|
133
|
+
namespace: TestSkosNs,
|
|
134
|
+
to: :name
|
|
135
|
+
end
|
|
136
|
+
end)
|
|
137
|
+
|
|
138
|
+
expect { NoSubjModel.new(name: "test").to_turtle }
|
|
139
|
+
.to raise_error(Lutaml::Turtle::MissingSubjectError, /subject/)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "JSON-LD handles invalid JSON gracefully" do
|
|
143
|
+
stub_const("SimpleJsonLdModel", Class.new(Lutaml::Model::Serializable) do
|
|
144
|
+
attribute :name, :string
|
|
145
|
+
|
|
146
|
+
rdf do
|
|
147
|
+
namespace TestExNs
|
|
148
|
+
predicate :name, namespace: TestExNs, to: :name
|
|
149
|
+
end
|
|
150
|
+
end)
|
|
151
|
+
|
|
152
|
+
expect { SimpleJsonLdModel.from_jsonld("not valid json!!!") }
|
|
153
|
+
.to raise_error(Lutaml::Model::InvalidFormatError)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "Rdf::MappingRule validates namespace type" do
|
|
157
|
+
mapping = Lutaml::Rdf::Mapping.new
|
|
158
|
+
expect do
|
|
159
|
+
mapping.predicate(:foo, namespace: String, to: :bar)
|
|
160
|
+
end.to raise_error(ArgumentError, /Rdf::Namespace/)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "Rdf::MappingRule requires :to parameter" do
|
|
164
|
+
mapping = Lutaml::Rdf::Mapping.new
|
|
165
|
+
expect do
|
|
166
|
+
mapping.predicate(:foo, namespace: TestSkosNs, to: nil)
|
|
167
|
+
end.to raise_error(ArgumentError, /required/)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "lutaml/jsonld"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Lutaml::JsonLd::Adapter do
|
|
7
|
+
let(:jsonld_hash) do
|
|
8
|
+
{
|
|
9
|
+
"@context" => { "name" => "http://example.org/name" },
|
|
10
|
+
"@type" => "Thing",
|
|
11
|
+
"name" => "test",
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe ".parse" do
|
|
16
|
+
it "parses valid JSON-LD string to hash" do
|
|
17
|
+
json = JSON.generate(jsonld_hash)
|
|
18
|
+
result = described_class.parse(json)
|
|
19
|
+
expect(result).to eq(jsonld_hash.transform_keys(&:to_s))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "#to_jsonld" do
|
|
24
|
+
it "generates JSON-LD string from hash" do
|
|
25
|
+
adapter = described_class.new(jsonld_hash)
|
|
26
|
+
result = adapter.to_jsonld
|
|
27
|
+
parsed = JSON.parse(result)
|
|
28
|
+
expect(parsed["@context"]).to eq({ "name" => "http://example.org/name" })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "supports pretty generation" do
|
|
32
|
+
adapter = described_class.new(jsonld_hash)
|
|
33
|
+
result = adapter.to_jsonld(pretty: true)
|
|
34
|
+
expect(result).to include("\n")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "round-trips parse → generate" do
|
|
39
|
+
json = JSON.generate(jsonld_hash)
|
|
40
|
+
parsed = described_class.parse(json)
|
|
41
|
+
adapter = described_class.new(parsed)
|
|
42
|
+
result = adapter.to_jsonld
|
|
43
|
+
round_tripped = JSON.parse(result)
|
|
44
|
+
expect(round_tripped).to eq(jsonld_hash.transform_keys(&:to_s))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "lutaml/jsonld"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Lutaml::JsonLd::Context do
|
|
7
|
+
subject(:ctx) { described_class.new }
|
|
8
|
+
|
|
9
|
+
describe "empty context" do
|
|
10
|
+
it "serializes to empty hash" do
|
|
11
|
+
expect(ctx.to_hash).to eq({})
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#prefix" do
|
|
16
|
+
it "adds namespace prefix from Rdf::Namespace class" do
|
|
17
|
+
ctx.prefix(Lutaml::Rdf::Namespaces::SkosNamespace)
|
|
18
|
+
expect(ctx.to_hash).to include("skos" => "http://www.w3.org/2004/02/skos/core#")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "adds multiple prefixes" do
|
|
22
|
+
ctx.prefix(Lutaml::Rdf::Namespaces::SkosNamespace)
|
|
23
|
+
ctx.prefix(Lutaml::Rdf::Namespaces::DctermsNamespace)
|
|
24
|
+
hash = ctx.to_hash
|
|
25
|
+
expect(hash).to include("skos" => "http://www.w3.org/2004/02/skos/core#")
|
|
26
|
+
expect(hash).to include("dcterms" => "http://purl.org/dc/terms/")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#vocab" do
|
|
31
|
+
it "sets @vocab" do
|
|
32
|
+
ctx.vocab("http://example.org/ns/")
|
|
33
|
+
expect(ctx.to_hash).to include("@vocab" => "http://example.org/ns/")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "#language" do
|
|
38
|
+
it "sets @language" do
|
|
39
|
+
ctx.language("en")
|
|
40
|
+
expect(ctx.to_hash).to include("@language" => "en")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "#base" do
|
|
45
|
+
it "sets @base" do
|
|
46
|
+
ctx.base("http://example.org/")
|
|
47
|
+
expect(ctx.to_hash).to include("@base" => "http://example.org/")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "#term" do
|
|
52
|
+
it "adds simple term as name => id" do
|
|
53
|
+
ctx.term("name", id: "http://example.org/name")
|
|
54
|
+
expect(ctx.to_hash).to include("name" => "http://example.org/name")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "adds term with type" do
|
|
58
|
+
ctx.term("date", id: "http://example.org/date", type: "xsd:date")
|
|
59
|
+
expect(ctx.to_hash).to include("date" => {
|
|
60
|
+
"@id" => "http://example.org/date", "@type" => "xsd:date"
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "adds term with container" do
|
|
65
|
+
ctx.term("labels", id: "http://example.org/labels", container: :language)
|
|
66
|
+
expect(ctx.to_hash).to include("labels" => {
|
|
67
|
+
"@id" => "http://example.org/labels", "@container" => "@language"
|
|
68
|
+
})
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "adds term with reverse" do
|
|
72
|
+
ctx.term("parent", id: "http://example.org/parent", reverse: true)
|
|
73
|
+
expect(ctx.to_hash).to include("parent" => {
|
|
74
|
+
"@id" => "http://example.org/parent", "@reverse" => true
|
|
75
|
+
})
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe "#to_hash" do
|
|
80
|
+
it "serializes complete context" do
|
|
81
|
+
ctx.prefix(Lutaml::Rdf::Namespaces::SkosNamespace)
|
|
82
|
+
ctx.vocab("http://example.org/ns/")
|
|
83
|
+
ctx.term("name", id: "http://example.org/name")
|
|
84
|
+
hash = ctx.to_hash
|
|
85
|
+
expect(hash["@vocab"]).to eq("http://example.org/ns/")
|
|
86
|
+
expect(hash["skos"]).to eq("http://www.w3.org/2004/02/skos/core#")
|
|
87
|
+
expect(hash["name"]).to eq("http://example.org/name")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "#resolve" do
|
|
92
|
+
before do
|
|
93
|
+
ctx.prefix(Lutaml::Rdf::Namespaces::SkosNamespace)
|
|
94
|
+
ctx.vocab("http://example.org/ns/")
|
|
95
|
+
ctx.term("status", id: "http://example.org/status")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "resolves compact IRI via prefixes" do
|
|
99
|
+
expect(ctx.resolve("skos:prefLabel")).to eq("http://www.w3.org/2004/02/skos/core#prefLabel")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "resolves via term definitions" do
|
|
103
|
+
expect(ctx.resolve("status")).to eq("http://example.org/status")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "resolves unprefixed name via @vocab" do
|
|
107
|
+
expect(ctx.resolve("unknown")).to eq("http://example.org/ns/unknown")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "returns nil for unknown prefix" do
|
|
111
|
+
expect(ctx.resolve("unknown:something")).to be_nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "lutaml/jsonld"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Lutaml::JsonLd::TermDefinition do
|
|
7
|
+
it "simple term maps to name => id" do
|
|
8
|
+
td = described_class.new(name: "name", id: "http://example.org/name")
|
|
9
|
+
expect(td.to_context_hash).to eq("name" => "http://example.org/name")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "term with type includes @type" do
|
|
13
|
+
td = described_class.new(name: "date", id: "http://example.org/date",
|
|
14
|
+
type: "xsd:date")
|
|
15
|
+
expect(td.to_context_hash).to eq("date" => {
|
|
16
|
+
"@id" => "http://example.org/date", "@type" => "xsd:date"
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "term with container includes @container" do
|
|
21
|
+
td = described_class.new(name: "labels", id: "http://example.org/labels",
|
|
22
|
+
container: :language)
|
|
23
|
+
expect(td.to_context_hash).to eq("labels" => {
|
|
24
|
+
"@id" => "http://example.org/labels", "@container" => "@language"
|
|
25
|
+
})
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "term with language includes @language" do
|
|
29
|
+
td = described_class.new(name: "title", id: "http://example.org/title",
|
|
30
|
+
language: "en")
|
|
31
|
+
expect(td.to_context_hash).to eq("title" => {
|
|
32
|
+
"@id" => "http://example.org/title", "@language" => "en"
|
|
33
|
+
})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "term with reverse includes @reverse" do
|
|
37
|
+
td = described_class.new(name: "parent", id: "http://example.org/parent",
|
|
38
|
+
reverse: true)
|
|
39
|
+
expect(td.to_context_hash).to eq("parent" => {
|
|
40
|
+
"@id" => "http://example.org/parent", "@reverse" => true
|
|
41
|
+
})
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "complex term includes all fields" do
|
|
45
|
+
td = described_class.new(
|
|
46
|
+
name: "date",
|
|
47
|
+
id: "http://example.org/date",
|
|
48
|
+
type: "xsd:date",
|
|
49
|
+
container: :set,
|
|
50
|
+
)
|
|
51
|
+
hash = td.to_context_hash
|
|
52
|
+
expect(hash["date"]).to eq({ "@id" => "http://example.org/date",
|
|
53
|
+
"@type" => "xsd:date", "@container" => "@set" })
|
|
54
|
+
end
|
|
55
|
+
end
|