Add documentation on RDF.Resource.Generators

This commit is contained in:
Marcel Otto 2022-03-11 13:19:25 +01:00
parent 78a3e25bd8
commit 7d97888971
5 changed files with 148 additions and 19 deletions

View file

@ -12,6 +12,7 @@ defmodule RDF do
- the `RDF.Literal.Datatype` system
- a facility for the mapping of URIs of a vocabulary to Elixir modules and
functions: `RDF.Vocabulary.Namespace`
- a facility for the automatic generation of resource identifiers: `RDF.Resource.Generator`
- modules for the construction of statements
- `RDF.Triple`
- `RDF.Quad`

View file

@ -2,6 +2,10 @@ defmodule RDF.BlankNode do
@moduledoc """
An RDF blank node (aka bnode) is a local node of a graph without an IRI.
This module can also be used as `RDF.Resource.Generator` for the generation
of random identifiers, which is using the `new/0` function.
For the generation of value-based blank nodes, you can use `RDF.BlankNode.Generator`.
see <https://www.w3.org/TR/rdf11-primer/#section-blank-node>
and <https://www.w3.org/TR/rdf11-concepts/#section-blank-nodes>
"""
@ -14,10 +18,10 @@ defmodule RDF.BlankNode do
defstruct [:value]
use RDF.Resource.Generator
alias RDF.Resource.Generator.ConfigError
alias RDF.Resource.Generator
@doc """
Creates a `RDF.BlankNode`.
Creates a random `RDF.BlankNode`.
"""
@spec new :: t
def new, do: new(:erlang.unique_integer([:positive]))
@ -47,20 +51,6 @@ defmodule RDF.BlankNode do
"""
def value(%__MODULE__{} = bnode), do: bnode.value
@impl RDF.Resource.Generator
def generate(_), do: new()
@impl RDF.Resource.Generator
def generate(_, _) do
raise(
ConfigError,
"""
Value-based resource generation is not supported by RDF.BlankNode.
Use RDF.BlankNode.Generator or another generator.
"""
)
end
@doc """
Tests for value equality of blank nodes.
@ -75,6 +65,20 @@ defmodule RDF.BlankNode do
def equal_value?(_, _),
do: nil
@impl RDF.Resource.Generator
def generate(_), do: new()
@impl RDF.Resource.Generator
def generate(_, _) do
raise(
Generator.ConfigError,
"""
Value-based resource generation is not supported by RDF.BlankNode.
Use RDF.BlankNode.Generator or another generator.
"""
)
end
defimpl String.Chars do
def to_string(bnode), do: "_:#{bnode.value}"
end

View file

@ -1,6 +1,11 @@
defmodule RDF.BlankNode.Generator do
@moduledoc """
A GenServer generates `RDF.BlankNode`s using a `RDF.BlankNode.Generator.Algorithm`.
A GenServer which generates `RDF.BlankNode`s using a `RDF.BlankNode.Generator.Algorithm`.
This module implements the `RDF.Resource.Generator` behaviour.
The only `RDF.Resource.Generator` configuration it requires is the process
identifier. The actual configuration of the behaviour of this generator
is done on the GenServer itself via `start_link/1` and `start/1`.
"""
use GenServer
@ -11,7 +16,15 @@ defmodule RDF.BlankNode.Generator do
@doc """
Starts a blank node generator linked to the current process.
The state will be initialized according to the given `RDF.BlankNode.Generator.Algorithm`.
The `RDF.BlankNode.Generator.Algorithm` implementation is the only required
keyword option, which must be given with the `:algorithm` key or, if no other
options are required, can be given directly (instead of a keyword list).
The remaining options are used as the configuration for `init/1` of the
respective `RDF.BlankNode.Generator.Algorithm` implementation.
If you want to pass `GenServer.start_link/3` options, you'll can provide
two separate keyword lists as a tuple with the first being the `RDF.BlankNode.Generator`
configuration and the second the `GenServer.start_link/3` options.
"""
def start_link(algorithm) when is_atom(algorithm) do
start_link({[algorithm: algorithm], []})
@ -31,7 +44,7 @@ defmodule RDF.BlankNode.Generator do
@doc """
Starts a blank node generator process without links (outside of a supervision tree).
The state will be initialized according to the given `RDF.BlankNode.Generator.Algorithm`.
The options are handled the same as `start_link/1`.
"""
def start(algorithm) when is_atom(algorithm) do
start({[algorithm: algorithm], []})

View file

@ -1,10 +1,61 @@
defmodule RDF.Resource.Generator do
@moduledoc """
A configurable and customizable way to generate resource identifiers.
The basis are different implementations of the behaviour defined in this
module for configurable resource identifier generation methods.
Generally two kinds of identifiers are differentiated:
1. parameter-less identifiers which are generally random
2. identifiers which are based on some value, where every attempt to create
an identifier for the same value, should produce the same identifier
Not all implementations must support both kind of identifiers.
The `RDF.Resource.Generator` module provides two `generate` functions for the
kindes of identifiers, `generate/1` for random-based and `generate/2` for
value-based identifiers.
The `config` keyword list they take must contain a `:generator` key, which
provides the module implementing the `RDF.Resource.Generator` behaviour.
All other keywords are specific to the generator implementation.
When the generator is configured differently for the different
identifier types, the identifier-type specific configuration can be put under
the keys `:random_based` and `:value_based` respectively.
The `RDF.Resource.Generator.generate` implementations will be called with the
general configuration options from the top-level merged with the identifier-type
specific configuration.
The `generate` functions however are usually not called directly.
See the [guide](https://rdf-elixir.dev/rdf-ex/resource-generators.html) on
how they are meant to be used.
The following `RDF.Resource.Generator` implementations are provided with RDF.ex:
- `RDF.BlankNode`
- `RDF.BlankNode.Generator`
- `RDF.IRI.UUID.Generator`
"""
@type id_type :: :random_based | :value_based
@doc """
Generates a random resource identifier based on the given `config`.
"""
@callback generate(config :: any) :: RDF.Resource.t()
@doc """
Generates a resource identifier based on the given `config` and `value`.
"""
@callback generate(config :: any, value :: binary) :: RDF.Resource.t()
@doc """
Allows to normalize the configuration.
This callback is optional. A default implementation is generated which
returns the configuration as-is.
"""
@callback generator_config(id_type, keyword) :: any
defmacro __using__(_opts) do
@ -18,11 +69,23 @@ defmodule RDF.Resource.Generator do
end
end
@doc """
Generates a random resource identifier based on the given `config`.
See the [guide](https://rdf-elixir.dev/rdf-ex/resource-generators.html) on
how it is meant to be used.
"""
def generate(config) do
{generator, config} = config(:random_based, config)
generator.generate(config)
end
@doc """
Generates a resource identifier based on the given `config` and `value`.
See the [guide](https://rdf-elixir.dev/rdf-ex/resource-generators.html) on
how it is meant to be used.
"""
def generate(config, value) do
{generator, config} = config(:value_based, config)
generator.generate(config, value)

View file

@ -4,6 +4,54 @@
if Code.ensure_loaded?(UUID) do
defmodule RDF.IRI.UUID.Generator do
@moduledoc """
A `RDF.Resource.Generator` for various kinds of UUID-based IRI identifiers.
This generator is only available when you have defined the [elixir_uuid](https://hex.pm/packages/elixir_uuid)
package as dependency in the Mixfile of your application.
## Configuration options
- `:prefix`: The URI prefix to be prepended to the generated UUID.
It can be given also as `RDF.Vocabulary.Namespace` module.
If the `:uuid_format` is set explicitly to something other than `:urn`
(which is the default), this is a required parameter.
- `:uuid_version`: The UUID version to be used. Can be any of the
integers 1 and 4 for random-based identifiers (4 being the default) and
3 and 5 for value-based identifiers (5 being the default).
- `:uuid_format`: The format of the UUID to be generated. Can be any of the
following atoms:
- `:urn`: a standard UUID representation, prefixed with the UUID URN
(in this case the `:prefix` is not used) (the default when no `:prefix` given)
- `:default`: a standard UUID representation, appended to the `:prefix` value
(the default when a `:prefix` is given)
- `:hex`: a standard UUID without the `-` (dash) characters, appended to the
`:prefix` value
- `:uuid_namespace` (only with `:uuid_version` 3 and 5, where it is a required parameter)
When your generator configuration is just for a function producing one of
the two kinds of identifiers, you can use these options directly. Otherwise you
must provide the identifier-specific configuration under one of the keys
`:random_based` and `:value_based`.
## Example configuration
config :example, :id,
generator: RDF.IRI.UUID.Generator,
prefix: "http://example.com/",
uuid_format: :hex,
random_based: [
uuid_version: 1
],
value_based: [
uuid_version: 3,
uuid_namespace: UUID.uuid5(:url, "http://your.application.com/example")
]
"""
use RDF.Resource.Generator
alias RDF.IRI