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

rdf-elixir / rdf-ex / e13c6dddfa3dcff42f19061d70f6a1c7c0caafb8-PR-17

pending completion
e13c6dddfa3dcff42f19061d70f6a1c7c0caafb8-PR-17

Pull #17

github

marcelotto
Pull Request #17: Add implementation of the RDF Dataset canonicalization algorithm

166 of 166 new or added lines in 9 files covered. (100.0%)

5349 of 7224 relevant lines covered (74.04%)

567.51 hits per line

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

83.87
/lib/rdf/vocabulary/namespace/case_validation.ex
1
defmodule RDF.Vocabulary.Namespace.CaseValidation do
2
  @moduledoc false
3

4
  defmodule CaseViolationError do
5
    defexception [:message, label: "Case violations"]
6
  end
7

8
  import RDF.Utils, only: [downcase?: 1]
9

10
  alias RDF.Vocabulary.Namespace.TermMapping
11

12
  def validate_case(term_mapping)
13

14
  def validate_case(%{term_classification: nil} = term_mapping), do: term_mapping
62✔
15

16
  def validate_case(%{case_violation_handling: :ignore} = term_mapping), do: term_mapping
1✔
17

18
  def validate_case(term_mapping) do
19
    handle_case_violations(term_mapping, detect_case_violations(term_mapping))
28✔
20
  end
21

22
  defp detect_case_violations(term_mapping) do
23
    Enum.filter(term_mapping.terms, fn
28✔
24
      {term, true} ->
25
        if term not in term_mapping.aliased_terms,
54✔
26
          do: improper_case?(term_mapping, Atom.to_string(term), term)
41✔
27

28
      {term, original_term} ->
29
        improper_case?(term_mapping, Atom.to_string(term), String.to_atom(original_term))
17✔
30
    end)
31
  end
32

33
  defp improper_case?(_, "_" <> _, _), do: false
2✔
34

35
  defp improper_case?(%{allow_lowercase_resource_terms: false} = term_mapping, term, original) do
36
    case term_mapping.term_classification[original] do
50✔
37
      :property -> not downcase?(term)
23✔
38
      :resource -> downcase?(term)
25✔
39
      nil -> downcase?(term)
2✔
40
    end
41
  end
42

43
  defp improper_case?(%{allow_lowercase_resource_terms: true} = term_mapping, term, original) do
44
    term_mapping.term_classification[original] == :property and not downcase?(term)
6✔
45
  end
46

47
  defp group_case_violations(violations) do
48
    violations
49
    |> Enum.group_by(fn
10✔
50
      {term, true} -> if downcase?(term), do: :lowercased_term, else: :capitalized_term
13✔
51
      {term, _original} -> if downcase?(term), do: :lowercased_alias, else: :capitalized_alias
3✔
52
    end)
53
  end
54

55
  defp handle_case_violations(term_mapping, []), do: term_mapping
18✔
56

57
  defp handle_case_violations(term_mapping, violations) do
58
    do_handle_case_violations(
10✔
59
      term_mapping,
60
      group_case_violations(violations),
61
      term_mapping.case_violation_handling
10✔
62
    )
63
  end
64

65
  defp do_handle_case_violations(term_mapping, grouped_violations, _)
66
       when map_size(grouped_violations) == 0,
67
       do: term_mapping
5✔
68

69
  defp do_handle_case_violations(term_mapping, violations, :fail) do
70
    resource_name_violations = fn violations ->
5✔
71
      Enum.map_join(violations, "\n- ", fn {term, true} ->
2✔
72
        term_mapping |> TermMapping.term_to_iri(term) |> to_string()
2✔
73
      end)
74
    end
75

76
    alias_violations = fn violations ->
5✔
77
      Enum.map_join(violations, "\n- ", fn {term, original} ->
3✔
78
        "alias #{term} for #{TermMapping.term_to_iri(term_mapping, original)}"
3✔
79
      end)
80
    end
81

82
    violation_error_lines =
5✔
83
      Enum.map_join(violations, fn
84
        {:capitalized_term, violations} ->
85
          """
1✔
86
          Terms for properties should be lowercased, but the following properties are
87
          capitalized:
88

89
          - #{resource_name_violations.(violations)}
1✔
90

91
          """
92

93
        {:lowercased_term, violations} ->
94
          """
1✔
95
          Terms for non-property resource should be capitalized, but the following
96
          non-properties are lowercased:
97

98
          - #{resource_name_violations.(violations)}
1✔
99

100
          """
101

102
        {:capitalized_alias, violations} ->
103
          """
2✔
104
          Terms for properties should be lowercased, but the following aliases for
105
          properties are capitalized:
106

107
          - #{alias_violations.(violations)}
2✔
108

109
          """
110

111
        {:lowercased_alias, violations} ->
112
          """
1✔
113
          Terms for non-property resource should be capitalized, but the following
114
          aliases for non-properties are lowercased:
115

116
          - #{alias_violations.(violations)}
1✔
117

118
          """
119
      end)
120

121
    TermMapping.add_error(term_mapping, CaseViolationError, """
5✔
122
    #{violation_error_lines}
5✔
123
    You have the following options:
124

125
    - if you are in control of the vocabulary, consider renaming the resource
126
    - define a properly cased alias with the :alias option on defvocab
127
    - change the handling of case violations with the :case_violations option on defvocab
128
    - ignore the resource with the :ignore option on defvocab
129
    """)
130
  end
131

132
  defp do_handle_case_violations(term_mapping, violation_groups, :warn) do
133
    for {type, violations} <- violation_groups,
×
134
        {term, original} <- violations do
×
135
      case_violation_warning(type, term, original, term_mapping)
×
136
    end
137

138
    term_mapping
×
139
  end
140

141
  defp do_handle_case_violations(term_mapping, violation_groups, :auto_fix) do
142
    do_handle_case_violations(term_mapping, violation_groups, &auto_fix_term/2)
2✔
143
  end
144

145
  defp do_handle_case_violations(term_mapping, violation_groups, fun)
146
       when is_function(fun) do
147
    {alias_violations, term_violations} =
6✔
148
      Map.split(violation_groups, [:capitalized_alias, :lowercased_alias])
149

150
    term_mapping = do_handle_case_violations(term_mapping, alias_violations, :fail)
6✔
151

152
    Enum.reduce(term_violations, term_mapping, fn {group, violations}, term_mapping ->
6✔
153
      type =
9✔
154
        case group do
155
          :capitalized_term -> :property
5✔
156
          :lowercased_term -> :resource
4✔
157
        end
158

159
      Enum.reduce(violations, term_mapping, fn {term, _}, term_mapping ->
9✔
160
        case fun.(type, Atom.to_string(term)) do
11✔
161
          :ignore -> TermMapping.ignore_term(term_mapping, term)
2✔
162
          {:error, error} -> raise error
×
163
          {:ok, alias} -> TermMapping.add_alias(term_mapping, term, alias)
9✔
164
        end
165
      end)
166
    end)
167
  end
168

169
  defp do_handle_case_violations(term_mapping, violation_groups, {mod, fun})
170
       when is_atom(mod) and is_atom(fun) do
171
    do_handle_case_violations(term_mapping, violation_groups, &apply(mod, fun, [&1, &2]))
2✔
172
  end
173

174
  defp do_handle_case_violations(term_mapping, violation_groups, {mod, fun, args})
175
       when is_atom(mod) and is_atom(fun) and is_list(args) do
176
    do_handle_case_violations(term_mapping, violation_groups, &apply(mod, fun, [&1, &2 | args]))
1✔
177
  end
178

179
  defp case_violation_warning(:capitalized_term, term, _, term_mapping) do
180
    IO.warn("'#{TermMapping.term_to_iri(term_mapping, term)}' is a capitalized property")
×
181
  end
182

183
  defp case_violation_warning(:lowercased_term, term, _, term_mapping) do
184
    IO.warn(
×
185
      "'#{TermMapping.term_to_iri(term_mapping, term)}' is a lowercased non-property resource"
×
186
    )
187
  end
188

189
  defp case_violation_warning(:capitalized_alias, term, _, _) do
190
    IO.warn("capitalized alias '#{term}' for a property")
×
191
  end
192

193
  defp case_violation_warning(:lowercased_alias, term, _, _) do
194
    IO.warn("lowercased alias '#{term}' for a non-property resource")
×
195
  end
196

197
  defp auto_fix_term(:property, term) do
198
    {first, rest} = String.next_grapheme(term)
2✔
199
    {:ok, String.downcase(first) <> rest}
200
  end
201

202
  defp auto_fix_term(:resource, term), do: {:ok, :string.titlecase(term)}
1✔
203
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