From f8517de119c301f7ace4ae3ffccc7c5b29f557fb Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 5 Jun 2022 03:09:10 +0200 Subject: [PATCH] Support more values for the :base_iri in defvocab --- CHANGELOG.md | 10 ++++++-- lib/rdf/vocabulary_namespace.ex | 21 +++++++++++---- test/unit/vocabulary_namespace_test.exs | 34 +++++++++++++++---------- 3 files changed, 45 insertions(+), 20 deletions(-) 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