diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44ac277..6bb19d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,15 +18,21 @@ The generated namespaces are much more flexible now and compile faster.
### Changed
+- The `:base_iri` specified in `defvocab` can now be given in any form supported
+ by `RDF.IRI.new/1`. There are also no longer restrictions on the expression
+ of this value. While previously the value had to be provided as a literal value,
+ now any expression returning a value accepted by `RDF.IRI.new/1` can be given
+ (e.g. function calls, module attributes etc.).
+ The `:base_iri` also no longer has to end with a `/` or `#`.
- Aliases on a `RDF.Vocabulary.Namespace` can now be specified directly in the
- `:terms` list
+ `:terms` list.
- When defining an alias for a term of vocabulary which would be invalid as an
Elixir term, the original term is now implicitly ignored and won't any longer
be returned by the `__terms__/0` function of a `RDF.Vocabulary.Namespace`.
- `RDF.Data.merge/2` and `RDF.Data.equal?/2` are now commutative, i.e. structs
which implement the `RDF.Data` protocol can be given also as the second argument
(previously custom structs with `RDF.Data` protocol implementations always
- had to be given as the first argument)
+ had to be given as the first argument).
- several performance improvements
diff --git a/lib/rdf/vocabulary_namespace.ex b/lib/rdf/vocabulary_namespace.ex
index f91d484..0f71d9c 100644
--- a/lib/rdf/vocabulary_namespace.ex
+++ b/lib/rdf/vocabulary_namespace.ex
@@ -9,7 +9,7 @@ defmodule RDF.Vocabulary.Namespace do
the `RDF.NS` module.
"""
- alias RDF.{Description, Graph, Dataset, Vocabulary, Namespace}
+ alias RDF.{Description, Graph, Dataset, Vocabulary, Namespace, IRI}
import RDF.Vocabulary.Namespace.{TermMapping, CaseValidation}
import RDF.Vocabulary, only: [term_to_iri: 2, extract_terms: 2]
@@ -254,14 +254,25 @@ defmodule RDF.Vocabulary.Namespace do
end
end
- defp normalize_base_uri(base_uri) do
- unless is_binary(base_uri) and String.ends_with?(base_uri, ~w[/ # .]) do
- raise RDF.Namespace.InvalidVocabBaseIRIError, "invalid base IRI: #{inspect(base_uri)}"
- else
+ defp normalize_base_uri(%IRI{} = base_iri), do: IRI.to_string(base_iri)
+
+ defp normalize_base_uri(base_uri) when is_binary(base_uri) do
+ if IRI.valid?(base_uri) do
base_uri
+ else
+ raise RDF.Namespace.InvalidVocabBaseIRIError, "invalid base IRI: #{inspect(base_uri)}"
end
end
+ defp normalize_base_uri(base_uri) do
+ base_uri |> IRI.new() |> normalize_base_uri()
+ rescue
+ [Namespace.UndefinedTermError, IRI.InvalidError, FunctionClauseError] ->
+ reraise RDF.Namespace.InvalidVocabBaseIRIError,
+ "invalid base IRI: #{inspect(base_uri)}",
+ __STACKTRACE__
+ end
+
@doc false
@spec vocabulary_namespace?(module) :: boolean
def vocabulary_namespace?(name) do
diff --git a/test/unit/vocabulary_namespace_test.exs b/test/unit/vocabulary_namespace_test.exs
index f1612cd..624c80c 100644
--- a/test/unit/vocabulary_namespace_test.exs
+++ b/test/unit/vocabulary_namespace_test.exs
@@ -42,6 +42,15 @@ defmodule RDF.Vocabulary.NamespaceTest do
base_iri: "http://example.com/strict#",
terms: ~w[foo bar]
+ @base_iri "http://example.com/"
+ defvocab ExampleWithBaseFromModuleAttribute,
+ base_iri: @base_iri,
+ terms: ~w[foo Bar]a
+
+ defvocab ExampleWithBaseFromIRI,
+ base_iri: ~I,
+ terms: ~w[foo Bar]a
+
defvocab ExampleFromGraph,
base_iri: "http://example.com/from_graph#",
data:
@@ -139,18 +148,6 @@ defmodule RDF.Vocabulary.NamespaceTest do
end
end
- test "when the base_iri doesn't end with '/' or '#', an error is raised" do
- assert_raise RDF.Namespace.InvalidVocabBaseIRIError, fn ->
- defmodule NSWithInvalidBaseIRI1 do
- use RDF.Vocabulary.Namespace
-
- defvocab Example,
- base_iri: "http://example.com/base_iri4",
- terms: []
- end
- end
- end
-
test "when the base_iri isn't a valid IRI, an error is raised" do
assert_raise RDF.Namespace.InvalidVocabBaseIRIError, fn ->
defmodule NSWithInvalidBaseIRI2 do
@@ -957,9 +954,13 @@ defmodule RDF.Vocabulary.NamespaceTest do
test "__base_iri__ returns the base_iri" do
alias TestNS.ExampleFromGraph, as: HashVocab
alias TestNS.ExampleFromNTriplesFile, as: SlashVocab
+ alias TestNS.ExampleWithBaseFromModuleAttribute, as: BaseFromModuleAttribute
+ alias TestNS.ExampleWithBaseFromIRI, as: BaseFromIRI
assert HashVocab.__base_iri__() == "http://example.com/from_graph#"
assert SlashVocab.__base_iri__() == "http://example.com/from_ntriples/"
+ assert BaseFromModuleAttribute.__base_iri__() == "http://example.com/"
+ assert BaseFromIRI.__base_iri__() == "http://example.com/"
end
test "__iris__ returns all IRIs of the vocabulary" do
@@ -1040,7 +1041,9 @@ defmodule RDF.Vocabulary.NamespaceTest do
EXS,
ExampleFromGraph,
ExampleFromNTriplesFile,
- StrictExampleFromTerms
+ StrictExampleFromTerms,
+ ExampleWithBaseFromIRI,
+ ExampleWithBaseFromModuleAttribute
}
test "undefined terms" do
@@ -1088,12 +1091,17 @@ defmodule RDF.Vocabulary.NamespaceTest do
assert StrictExampleFromTerms.foo() == ~I
assert RDF.iri(StrictExampleFromTerms.foo()) == ~I
+
+ assert ExampleWithBaseFromIRI.foo() == ~I
+ assert ExampleWithBaseFromModuleAttribute.foo() == ~I
end
test "capitalized terms" do
assert RDF.iri(ExampleFromGraph.Bar) == ~I
assert RDF.iri(ExampleFromNTriplesFile.Bar) == ~I
assert RDF.iri(StrictExampleFromTerms.Bar) == ~I
+ assert RDF.iri(ExampleWithBaseFromIRI.Bar) == ~I
+ assert RDF.iri(ExampleWithBaseFromModuleAttribute.Bar) == ~I
end
test "terms starting with an underscore" do