• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rdf-elixir / jsonld-ex / de3ca9b1dae3a8dffda3dec6d69d62a58ee3622e

10 Apr 2025 03:31PM UTC coverage: 91.542%. Remained the same
de3ca9b1dae3a8dffda3dec6d69d62a58ee3622e

push

github

marcelotto
Remove unused HTML versions of test manifests

1775 of 1939 relevant lines covered (91.54%)

4387.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.65
/lib/json/ld/context.ex
1
defmodule JSON.LD.Context do
2
  @moduledoc """
3
  Implementation of the JSON-LD 1.1 _Context Processing Algorithm_.
4

5
  <https://www.w3.org/TR/json-ld11-api/#context-processing-algorithms>
6
  """
7

8
  import JSON.LD.{IRIExpansion, Utils}
9

10
  alias JSON.LD.Context.TermDefinition
11
  alias JSON.LD.{DocumentLoader, Options}
12

13
  alias RDF.IRI
14

15
  @type local :: map | String.t() | nil
16
  @type remote :: [map]
17

18
  @type t :: %__MODULE__{
19
          term_defs: map,
20
          base_iri: String.t() | nil | :not_present,
21
          original_base_url: String.t() | nil,
22
          api_base_iri: String.t() | nil,
23
          inverse_context: map | nil,
24
          previous_context: t | nil,
25
          vocabulary_mapping: String.t() | nil,
26
          default_language: String.t() | nil,
27
          base_direction: String.t() | nil
28
        }
29

30
  defstruct term_defs: %{},
31
            base_iri: :not_present,
32
            original_base_url: nil,
33
            # This is the base IRI set via options
34
            api_base_iri: nil,
35
            vocabulary_mapping: nil,
36
            default_language: nil,
37
            base_direction: nil,
38
            inverse_context: nil,
39
            previous_context: nil
40

41
  @max_contexts_loaded Application.compile_env(:json_ld, :max_contexts_loaded, 50)
42

43
  @spec base(t) :: String.t() | nil
44
  def base(%__MODULE__{base_iri: :not_present, api_base_iri: api_base_iri}),
45
    do: api_base_iri
2,740✔
46

47
  def base(%__MODULE__{base_iri: base_iri}),
48
    do: base_iri
2,524✔
49

50
  @spec language(t, String.t()) :: String.t() | nil
51
  def language(active, term) do
52
    case Map.get(active.term_defs, term, %TermDefinition{}).language_mapping do
×
53
      false -> active.default_language
×
54
      language -> language
×
55
    end
56
  end
57

58
  @spec new(Options.convertible()) :: t
59
  def new(options \\ %Options{}),
116✔
60
    do: %__MODULE__{api_base_iri: Options.new(options).base}
12,600✔
61

62
  @spec create(map, Options.convertible()) :: t
63
  def create(%{"@context" => json_ld_context}, options),
64
    do: options |> new() |> update(json_ld_context, options)
4,016✔
65

66
  defp init_options(options) do
67
    options
68
    # used to detect cyclical context inclusions
69
    |> Keyword.put_new(:remote_contexts, [])
70
    # used to allow changes to protected terms,
71
    |> Keyword.put_new(:override_protected, false)
72
    # to mark term definitions associated with non-propagated contexts
73
    |> Keyword.put_new(:propagate, true)
74
    #  used to limit recursion when validating possibly recursive scoped contexts..
75
    |> Keyword.put_new(:validate_scoped_context, true)
26,552✔
76
  end
77

78
  @spec update(t, [local] | local, Options.convertible()) :: t
79
  def update(active, local, options \\ [])
20✔
80

81
  def update(active, local, %Options{} = processor_options) do
82
    update(active, local, init_options([]), processor_options)
9,756✔
83
  end
84

85
  def update(active, local, options) do
86
    {processor_options, options} =
3,372✔
87
      case Keyword.pop(options, :processor_options) do
88
        {nil, options} -> Options.extract(options)
20✔
89
        {processor_options, options} -> {struct(processor_options, options), options}
3,352✔
90
      end
91

92
    update(active, local, init_options(options), processor_options)
3,372✔
93
  end
94

95
  @spec update(t, [local] | local, keyword, Options.convertible()) :: t
96
  def update(%__MODULE__{} = active, local, options, processor_options) when is_list(local) do
97
    # 1) Initialize result to the result of cloning active context, with inverse context set to null
98
    result = %{active | inverse_context: nil}
13,424✔
99

100
    # 3) If propagate is false, and result does not have a previous context, set previous context in result to active context
101
    result =
13,424✔
102
      if options[:propagate] == false && is_nil(active.previous_context) do
13,424✔
103
        %{result | previous_context: active}
780✔
104
      else
105
        result
12,644✔
106
      end
107

108
    {remote, options} = options |> init_options() |> Keyword.pop(:remote_contexts)
13,424✔
109
    # 5) For each item context in local context
110
    Enum.reduce(local, result, fn context, result ->
13,424✔
111
      do_update(result, context, remote, options, processor_options)
13,980✔
112
    end)
113
  end
114

115
  # 2) If local context has a @propagate entry, its value MUST be boolean true or false, set propagate to that value
116
  def update(%__MODULE__{}, %{"@propagate" => propagate}, _, _) when not is_boolean(propagate) do
117
    raise JSON.LD.Error.invalid_propagate_value(propagate)
12✔
118
  end
119

120
  def update(%__MODULE__{} = active, %{"@propagate" => propagate} = local, options, popts) do
121
    # 4) If local context is not an array, set it to an array containing only local context.
122
    update(active, [local], Keyword.put(options, :propagate, propagate), popts)
152✔
123
  end
124

125
  # 4) If local context is not an array, set it to an array containing only local context.
126
  def update(%__MODULE__{} = active, local, options, processor_options),
127
    do: update(active, [local], options, processor_options)
12,496✔
128

129
  # 5.1) If context is null
130
  @spec do_update(t, local, remote, keyword, Options.t()) :: t
131
  defp do_update(%__MODULE__{} = active, nil, _, options, processor_options) do
132
    # 5.1.1) If override protected is false and active context contains any protected term definitions, an invalid context nullification has been detected and processing is aborted.
133

134
    if not options[:override_protected] and
404✔
135
         active.term_defs |> Map.values() |> Enum.any?(& &1.protected) do
168✔
136
      raise JSON.LD.Error.invalid_context_nullification()
48✔
137
    else
138
      # 5.1.2) Initialize result as a newly-initialized active context, setting both base IRI and original base URL to the value of original base URL in active context, and, if propagate is false, previous context in result to the previous value of result.
139
      %{
140
        new(processor_options)
141
        | base_iri: active.original_base_url || :not_present,
356✔
142
          original_base_url: active.original_base_url,
356✔
143
          previous_context: if(!options[:propagate], do: active)
356✔
144
      }
145
    end
146
  end
147

148
  # 5.2) If context is a string
149
  defp do_update(%__MODULE__{} = active, context, remote, options, processor_options)
150
       when is_binary(context) do
151
    # 5.2.1) Initialize context to the result of resolving context against base URL. If base URL is not a valid IRI, then context MUST be a valid IRI, otherwise a loading document failed error has been detected and processing is aborted.
152
    # SPEC ISSUE: we try the unmentioned original_base_url here to get expand#tc031 pass
153
    base = active.original_base_url || processor_options.base || base(active)
416✔
154

155
    context =
416✔
156
      cond do
157
        IRI.absolute?(context) ->
158
          if IRI.valid?(context) do
124✔
159
            context
124✔
160
          else
161
            raise JSON.LD.Error.loading_document_failed("Invalid context IRI: #{context}")
×
162
          end
163

164
        not IRI.valid?(base) ->
292✔
165
          raise JSON.LD.Error.loading_document_failed("Invalid base IRI: #{base || "nil"}")
×
166

167
        true ->
292✔
168
          absolute_iri(context, base)
292✔
169
      end
170

171
    # 5.2.2) If validate scoped context is false, and remote contexts already includes context do not process context further and continue to any next context in local context.
172
    if not options[:validate_scoped_context] and context in remote do
416✔
173
      context
84✔
174
    else
175
      # 5.2.3) If the number of entries in the remote contexts array exceeds a processor defined limit, a context overflow error has been detected and processing is aborted;
176
      if length(remote) > @max_contexts_loaded do
332✔
177
        raise JSON.LD.Error.context_overflow(context)
×
178
      end
179

180
      # 5.2.3) otherwise, add context to remote contexts.
181
      remote = [context | remote]
332✔
182

183
      # 5.2.5)
184
      {loaded_context, document_url} = dereference_context(context, processor_options)
332✔
185

186
      # If context was previously dereferenced, processors MUST make provisions for retaining the base URL of that context for this step to enable the resolution of any relative context URLs that may be encountered during processing.
187
      %{
188
        active
189
        | base_iri: if(active.base_iri == :not_present, do: document_url, else: active.base_iri),
308✔
190
          original_base_url: document_url
191
      }
192
      # 5.2.6)
193
      |> update(
308✔
194
        loaded_context,
195
        Keyword.put(options, :remote_contexts, remote),
196
        processor_options |> Options.set_base(document_url)
197
      )
198
    end
199
  end
200

201
  # 5.4) Otherwise, context is a context definition
202
  defp do_update(%__MODULE__{} = active, local, remote, options, processor_options)
203
       when is_map(local) do
204
    {import_ctx, local} = Map.pop(local, "@import", :not_present)
13,152✔
205
    local = process_import(import_ctx, local, active, processor_options)
13,152✔
206

207
    {version, local} = Map.pop(local, "@version", :not_present)
13,104✔
208
    {base, local} = Map.pop(local, "@base", :not_present)
13,104✔
209
    {vocab, local} = Map.pop(local, "@vocab", :not_present)
13,104✔
210
    {language, local} = Map.pop(local, "@language", :not_present)
13,104✔
211
    {direction, local} = Map.pop(local, "@direction", :not_present)
13,104✔
212
    {propagate, local} = Map.pop(local, "@propagate", :not_present)
13,104✔
213
    {protected, local} = Map.pop(local, "@protected", false)
13,104✔
214

215
    active
216
    |> check_version(version, processor_options.processing_mode)
13,104✔
217
    |> set_base(base, remote)
218
    |> set_vocab(vocab, processor_options)
219
    |> set_language(language, processor_options)
220
    |> set_direction(direction, processor_options.processing_mode)
13,036✔
221
    |> validate_propagate(propagate, processor_options.processing_mode)
13,020✔
222
    |> create_term_definitions(
13,104✔
223
      local,
224
      options
225
      |> Keyword.put(:protected, protected)
226
      |> Keyword.put(:remote_contexts, remote),
227
      processor_options
228
    )
229
  end
230

231
  # 5.3) If context is not a map, an invalid local context error has been detected and processing is aborted.
232
  defp do_update(_, context, _, _, _) do
233
    raise JSON.LD.Error.invalid_local_context(context)
8✔
234
  end
235

236
  defp dereference_import(url, options) do
237
    # 5.6.4) (5.6.5 and 5.6.6 is done as part of dereference_context)
238
    {document, _url} = dereference_context(url, options)
148✔
239

240
    case document do
148✔
241
      # 5.6.7)
242
      %{"@import" => _} ->
243
        raise JSON.LD.Error.invalid_context_entry(
16✔
244
                "#{inspect(document)} must not include @import entry"
245
              )
246

247
      document ->
248
        document
132✔
249
    end
250
  end
251

252
  defp dereference_context(context_url, options) do
253
    {document, url} =
480✔
254
      case DocumentLoader.load(context_url, %{
255
             options
256
             | profile: "http://www.w3.org/ns/json-ld#context",
257
               request_profile: "http://www.w3.org/ns/json-ld#context"
258
           }) do
259
        {:ok, result} ->
464✔
260
          {result.document, result.document_url}
464✔
261

262
        {:error, %{__exception__: true} = exception} ->
263
          raise JSON.LD.Error.loading_remote_context_failed(
12✔
264
                  context_url,
265
                  Exception.message(exception)
266
                )
267

268
        {:error, reason} ->
269
          raise JSON.LD.Error.loading_remote_context_failed(context_url, reason)
4✔
270
      end
271

272
    document =
464✔
273
      cond do
274
        is_map(document) ->
275
          document
456✔
276

277
        # Note: This actually not necessary, as our document loaders always transform to "the internal representation", but custom document loaders may not.
278
        is_binary(document) ->
8✔
279
          case Jason.decode(document) do
×
280
            {:ok, result} ->
281
              result
×
282

283
            {:error, reason} ->
284
              raise JSON.LD.Error.invalid_remote_context(invalid: reason)
×
285
          end
286

287
        true ->
8✔
288
          raise JSON.LD.Error.invalid_remote_context(invalid: document)
8✔
289
      end
290

291
    {
292
      document["@context"] ||
456✔
293
        raise(JSON.LD.Error.invalid_remote_context("No @context key in #{inspect(document)}")),
×
294
      url
295
    }
296
  end
297

298
  defp check_version(active, :not_present, _), do: active
10,232✔
299

300
  # 5.5.2) If processing mode is set to json-ld-1.0, a processing mode conflict error has been detected and processing is aborted.
301
  defp check_version(_, 1.1, "json-ld-1.0") do
302
    raise JSON.LD.Error.processing_mode_conflict()
12✔
303
  end
304

305
  defp check_version(active, 1.1, _), do: active
2,836✔
306

307
  # 5.5.1) If the associated value is not 1.1, an invalid @version value has been detected, and processing is aborted.
308
  defp check_version(_, invalid, _) do
309
    raise JSON.LD.Error.invalid_version_value(invalid)
24✔
310
  end
311

312
  defp process_import(:not_present, context, _, _), do: context
12,980✔
313

314
  # 5.6.1) If processing mode is json-ld-1.0, an invalid context entry error has been detected and processing is aborted.
315
  defp process_import(import, _, _, %Options{processing_mode: "json-ld-1.0"}) do
316
    raise JSON.LD.Error.invalid_context_entry("@import with value #{inspect(import)}")
8✔
317
  end
318

319
  # 5.6.2) Otherwise, if the value of @import is not a string, an invalid @import value error has been detected and processing is aborted.
320
  defp process_import(import, _, _, _) when not is_binary(import) do
321
    raise JSON.LD.Error.invalid_import_value(import)
16✔
322
  end
323

324
  defp process_import(import, context, active, options) do
325
    import
326
    # 5.6.3) Initialize import to the result of resolving the value of @import against base URL.
327
    |> absolute_iri(base(active))
328
    # 5.6.4) Dereference import using the LoadDocumentCallback, passing import for url, and http://www.w3.org/ns/json-ld#context for profile and for requestProfile.
329
    |> dereference_import(options)
330
    |> case do
148✔
331
      # 5.6.8) Set context to the result of merging context into import context, replacing common entries with those from context.
332
      %{} = import_context ->
333
        Map.merge(import_context, context)
124✔
334

335
      invalid ->
336
        raise JSON.LD.Error.invalid_remote_context(invalid: invalid)
8✔
337
    end
338
  end
339

340
  # 5.7)
341
  defp set_base(active, :not_present, _), do: active
11,792✔
342

343
  defp set_base(active, _, remote) when is_list(remote) and length(remote) > 0,
344
    do: active
36✔
345

346
  defp set_base(active, nil, _), do: %__MODULE__{active | base_iri: nil}
24✔
347

348
  defp set_base(active, base, _) when is_binary(base) do
349
    cond do
1,208✔
350
      IRI.absolute?(base) ->
351
        %__MODULE__{active | base_iri: base}
1,168✔
352

353
      active_base = base(active) ->
40✔
354
        %__MODULE__{active | base_iri: absolute_iri(base, active_base)}
40✔
355

356
      true ->
×
357
        raise JSON.LD.Error.invalid_base_iri(base, :relative_without_active_base)
×
358
    end
359
  end
360

361
  defp set_base(_, invalid, _) do
362
    raise JSON.LD.Error.invalid_base_iri(invalid)
8✔
363
  end
364

365
  defp set_vocab(active, :not_present, _), do: active
9,584✔
366

367
  # 5.8.2) If value is null, remove any vocabulary mapping from result.
368
  defp set_vocab(active, nil, _), do: %__MODULE__{active | vocabulary_mapping: nil}
16✔
369

370
  # 5.8.3) Otherwise, if value is an IRI or blank node identifier, the vocabulary mapping of result is set to the result of IRI expanding value using true for document relative.
371
  defp set_vocab(active, vocab, options) do
372
    cond do
3,460✔
373
      # Note: The use of blank node identifiers to value for @vocab is obsolete, and may be removed in a future version of JSON-LD.
374
      blank_node_id?(vocab) ->
375
        %__MODULE__{active | vocabulary_mapping: vocab}
12✔
376

377
      not IRI.absolute?(vocab) and options.processing_mode == "json-ld-1.0" ->
3,448✔
378
        raise JSON.LD.Error.invalid_vocab_mapping(
×
379
                message: "@vocab must be an absolute IRI in 1.0 mode: #{inspect(vocab)}"
380
              )
381

382
      is_binary(vocab) ->
3,448✔
383
        # SPEC ISSUE: vocab must be set to true
384
        %__MODULE__{active | vocabulary_mapping: expand_iri(vocab, active, options, true, true)}
3,436✔
385

386
      true ->
12✔
387
        raise JSON.LD.Error.invalid_vocab_mapping(vocab)
12✔
388
    end
389
  end
390

391
  defp set_language(active, :not_present, _), do: active
12,788✔
392

393
  # 5.9.2) If value is null, remove any default language from result.
394
  defp set_language(active, nil, _), do: %__MODULE__{active | default_language: nil}
16✔
395

396
  # 5.9.3) Otherwise, if value is a string, the default language of result is set to value.
397
  defp set_language(active, language, popts) when is_binary(language) do
398
    %__MODULE__{active | default_language: validate_and_normalize_language(language, popts)}
232✔
399
  end
400

401
  # 5.9.3) If it is not a string, an invalid default language error has been detected and processing is aborted.
402
  defp set_language(_, language, _) do
403
    raise JSON.LD.Error.invalid_default_language(language)
12✔
404
  end
405

406
  defp set_direction(active, :not_present, _), do: active
12,892✔
407

408
  # 5.10.1) If processing mode is json-ld-1.0, an invalid context entry error has been detected and processing is aborted.
409
  defp set_direction(_, direction, "json-ld-1.0") do
410
    raise JSON.LD.Error.invalid_context_entry("@direction with value #{inspect(direction)}")
×
411
  end
412

413
  # 5.10.3) If value is null, remove any default language from result.
414
  defp set_direction(active, nil, _), do: %__MODULE__{active | base_direction: nil}
4✔
415

416
  # 5.10.4) Otherwise, if value is a string, the base direction of result is set to value. If it is not null, "ltr", or "rtl", an invalid base direction error has been detected and processing is aborted.
417
  defp set_direction(active, direction, _) when direction in ~w[ltr rtl],
418
    do: %__MODULE__{active | base_direction: String.to_atom(direction)}
124✔
419

420
  # 5.9.3) If it is not a string, an invalid default language error has been detected and processing is aborted.
421
  defp set_direction(_, direction, _) do
422
    raise JSON.LD.Error.invalid_base_direction(direction)
16✔
423
  end
424

425
  defp validate_propagate(active, :not_present, _), do: active
12,868✔
426

427
  # 5.11.1) If processing mode is json-ld-1.0, an invalid context entry error has been detected and processing is aborted.
428
  defp validate_propagate(_, propagate, "json-ld-1.0") do
429
    raise JSON.LD.Error.invalid_context_entry("@propagate with value #{inspect(propagate)}")
8✔
430
  end
431

432
  # 5.11.2) Otherwise, if the value of @propagate is not boolean true or false, an invalid @propagate value error has been detected and processing is aborted.
433
  defp validate_propagate(_, propagate, _) when not is_boolean(propagate) do
434
    raise JSON.LD.Error.invalid_propagate_value(propagate)
×
435
  end
436

437
  # Note: The previous context is actually set earlier in this algorithm (step 2 and 3)
438
  defp validate_propagate(active, _, _), do: active
144✔
439

440
  defp create_term_definitions(active, local, opts, popts, defined \\ %{}) do
441
    {active, _} =
13,012✔
442
      Enum.reduce(local, {active, defined}, fn {term, value}, {active, defined} ->
443
        TermDefinition.create(active, local, term, value, defined, popts, opts)
28,140✔
444
      end)
445

446
    active
12,232✔
447
  end
448

449
  @doc """
450
  Inverse Context Creation algorithm
451

452
  See <https://www.w3.org/TR/json-ld11-api/#inverse-context-creation>
453
  """
454
  @spec inverse(t) :: map
455
  def inverse(%__MODULE__{} = context) do
456
    # 2) Initialize default language to @none. If the active context has a default language, set default language to the default language from the active context normalized to lower case.
457
    default_language =
3,328✔
458
      (context.default_language && String.downcase(context.default_language)) || "@none"
3,328✔
459

460
    # 3) For each key term and value term definition in the active context:
461
    context.term_defs
3,328✔
462
    #    ordered by shortest term first (breaking ties by choosing the lexicographically least term)
463
    |> Enum.sort(fn {term1, _}, {term2, _} ->
464
      case {String.length(term1), String.length(term2)} do
28,536✔
465
        {length, length} -> term1 < term2
6,164✔
466
        {length1, length2} -> length1 < length2
22,372✔
467
      end
468
    end)
469
    |> Enum.reduce(%{}, fn {term, term_def}, result ->
3,328✔
470
      # 3.1) If the term definition is null, term cannot be selected during compaction, so continue to the next term.
471
      if term_def do
13,008✔
472
        # 3.2) Initialize container to @none. If the container mapping is not empty, set container to the concatenation of all values of the container mapping in lexicographical order.
473
        container =
13,008✔
474
          if term_def.container_mapping && !Enum.empty?(term_def.container_mapping) do
13,008✔
475
            term_def.container_mapping |> Enum.sort() |> Enum.join()
3,792✔
476
          else
477
            "@none"
478
          end
479

480
        # 3.3) Initialize var to the value of the IRI mapping for the term definition.
481
        var = term_def.iri_mapping
13,008✔
482

483
        type_map = get_in(result, [var, container, "@type"]) || %{}
13,008✔
484
        language_map = get_in(result, [var, container, "@language"]) || %{}
13,008✔
485
        any_map = get_in(result, [var, container, "@any"]) || %{"@none" => term}
13,008✔
486

487
        {type_map, language_map} =
13,008✔
488
          case term_def do
489
            # 3.10) If the term definition indicates that the term represents a reverse property
490
            %TermDefinition{reverse_property: true} ->
296✔
491
              {Map.put_new(type_map, "@reverse", term), language_map}
492

493
            # 3.11) Otherwise, if term definition has a type mapping which is @none
494
            %TermDefinition{type_mapping: "@none"} ->
224✔
495
              {
496
                Map.put_new(type_map, "@any", term),
497
                Map.put_new(language_map, "@any", term)
498
              }
499

500
            # 3.12) Otherwise, if term definition has a type mapping
501
            %TermDefinition{type_mapping: type_mapping} when type_mapping != false ->
3,724✔
502
              {Map.put_new(type_map, type_mapping, term), language_map}
503

504
            # 3.13) Otherwise, if term definition has both a language mapping and a direction mapping:
505
            %TermDefinition{
506
              language_mapping: language_mapping,
507
              direction_mapping: direction_mapping
508
            }
509
            when language_mapping != false and direction_mapping != false ->
510
              lang_dir =
296✔
511
                case {language_mapping, direction_mapping} do
512
                  {nil, nil} -> "@null"
×
513
                  {language_mapping, nil} -> String.downcase(language_mapping)
×
514
                  _ -> String.downcase("#{language_mapping}_#{direction_mapping}")
296✔
515
                end
516

517
              {type_map, Map.put_new(language_map, lang_dir, term)}
518

519
            # 3.14) Otherwise, if term definition has a language mapping (might be null)
520
            %TermDefinition{language_mapping: language_mapping} when language_mapping != false ->
521
              language = (language_mapping && String.downcase(language_mapping)) || "@null"
920✔
522
              {type_map, Map.put_new(language_map, language, term)}
523

524
            # 3.15) Otherwise, if term definition has a direction mapping (might be null)
525
            %TermDefinition{direction_mapping: direction_mapping} when direction_mapping != false ->
526
              direction = (direction_mapping && "_#{direction_mapping}") || "@none"
384✔
527
              {type_map, Map.put_new(language_map, direction, term)}
528

529
            _ ->
530
              # 3.16) Otherwise, if active context has a default base direction
531
              if context.base_direction do
7,164✔
532
                lang_dir = String.downcase("#{default_language}_#{context.base_direction}")
24✔
533

534
                {
535
                  Map.put_new(type_map, "@none", term),
536
                  language_map
537
                  |> Map.put_new(lang_dir, term)
538
                  |> Map.put_new("@none", term)
539
                }
540
              else
541
                # 3.17) Otherwise
542
                {
543
                  Map.put_new(type_map, "@none", term),
544
                  language_map
545
                  |> Map.put_new(default_language, term)
546
                  |> Map.put_new("@none", term)
547
                }
548
              end
549
          end
550

551
        result
552
        |> Map.put_new(var, %{})
553
        |> Map.update(var, %{}, fn container_map ->
13,008✔
554
          Map.put(container_map, container, %{
13,008✔
555
            "@type" => type_map,
556
            "@language" => language_map,
557
            "@any" => any_map
558
          })
559
        end)
560
      else
561
        result
×
562
      end
563
    end)
564
  end
565

566
  @spec set_inverse(t) :: t
567
  def set_inverse(%__MODULE__{inverse_context: nil} = context) do
568
    %__MODULE__{context | inverse_context: inverse(context)}
3,328✔
569
  end
570

571
  def set_inverse(%__MODULE__{} = context), do: context
15,244✔
572

573
  @spec empty?(t) :: boolean
574
  def empty?(%__MODULE__{
×
575
        term_defs: term_defs,
576
        vocabulary_mapping: nil,
577
        base_iri: :not_present,
578
        default_language: nil
579
      })
580
      when map_size(term_defs) == 0,
581
      do: true
582

583
  def empty?(_),
×
584
    do: false
585
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc