From 7d9788897192d79a18ca31d5aa213066f5b5467e Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Fri, 11 Mar 2022 13:19:25 +0100 Subject: [PATCH] Add documentation on RDF.Resource.Generators --- lib/rdf.ex | 1 + lib/rdf/blank_node.ex | 36 ++++++----- lib/rdf/blank_node/generator.ex | 19 +++++- lib/rdf/resource_generator.ex | 63 +++++++++++++++++++ .../resource_generators/iri_uuid_generator.ex | 48 ++++++++++++++ 5 files changed, 148 insertions(+), 19 deletions(-) diff --git a/lib/rdf.ex b/lib/rdf.ex index 7b9d6a2..470ccc7 100644 --- a/lib/rdf.ex +++ b/lib/rdf.ex @@ -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` diff --git a/lib/rdf/blank_node.ex b/lib/rdf/blank_node.ex index 22f4d45..8b87f20 100644 --- a/lib/rdf/blank_node.ex +++ b/lib/rdf/blank_node.ex @@ -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 and """ @@ -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 diff --git a/lib/rdf/blank_node/generator.ex b/lib/rdf/blank_node/generator.ex index 56693dc..49d9b64 100644 --- a/lib/rdf/blank_node/generator.ex +++ b/lib/rdf/blank_node/generator.ex @@ -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], []}) diff --git a/lib/rdf/resource_generator.ex b/lib/rdf/resource_generator.ex index 4aa2fde..5cd0420 100644 --- a/lib/rdf/resource_generator.ex +++ b/lib/rdf/resource_generator.ex @@ -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) diff --git a/lib/rdf/resource_generators/iri_uuid_generator.ex b/lib/rdf/resource_generators/iri_uuid_generator.ex index 3623eb8..05bbb46 100644 --- a/lib/rdf/resource_generators/iri_uuid_generator.ex +++ b/lib/rdf/resource_generators/iri_uuid_generator.ex @@ -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