Dmn.Resolver (dmn v0.1.0)

Copy Markdown View Source

How the models that a model imports are found.

An <import locationURI="…"> is a hint. It is text from a model that nobody vouches for, and dmn never opens it by itself: not as a path (../../../etc/passwd, an absolute path, a link that leaves a directory), and not as a URL. The caller says where models come from by giving a resolver, and there is none by default, so a model that has imports and is compiled without one has diagnostics for them.

A resolver is a function of the import and the model that has it, that returns the bytes of the imported model (they are read with Dmn.XML as any model is, with the same limits):

resolver = fn %Dmn.Model.Import{namespace: namespace}, _importer -> Map.fetch(my_models, namespace) end

map/1 and directory/2 make the usual ones. A resolver that reads files, or the network, is the caller's, and has to keep to what the caller allows; directory/2 is one that does.

Relative locations. A locationURI is a URI reference (RFC 3986), and a relative one is resolved against the base URI of the document that has it, normally the URI it was retrieved from (DMN 1.6, 13.3.2). So the same text in two models can be two documents, and what a resolver does with a location depends on the importer. A resolver that wants to say where a document is returns {:ok, bytes, uri} instead of {:ok, bytes}: uri is a string for that document (what it was retrieved from, in the resolver's own terms: the path below its root for directory/2), the same document always has the same one, and it is the base_uri of the model that was read, which the resolver is given in importer.base_uri when that model has imports of its own. The importer of the first model is the one the caller loaded, and has the base_uri that Dmn.load/2 was given (nil by default, which a resolver takes for the place its documents are rooted at). A resolver that gives no uri is asked the same, with a base_uri of nil for every importer, and can tell importers apart by what it likes of the model (its namespace, say).

Whatever the resolver says, the models are the same document when their bytes are, not when their uris are (Dmn.Imports): two imports of one namespace that come to other bytes are :import_conflict, and the uri is only recorded with the model, in Dmn.Compiled.imports. It is not in a diagnostic, which says what the importing model wrote and nothing of what is behind the resolver.

The imports are limited in number and in depth (Dmn.Limits: max_models, max_import_depth), and an import that cannot be had, is not a model, or is a model of another namespace than it says is a diagnostic of the importing model and not an exception.

Summary

Functions

A resolver that reads the files of a directory, and nothing outside it.

A resolver from a map of namespaces to the bytes of the model that has that namespace, or of locations to bytes. A model is looked up by its locationURI first, then by its namespace.

Types

t()

@type t() :: (Dmn.Model.Import.t(), Dmn.Model.t() ->
          {:ok, binary()}
          | {:ok, binary(), uri :: String.t()}
          | {:error, term()})

Functions

directory(root, options \\ [])

@spec directory(Path.t(), keyword()) :: t()

A resolver that reads the files of a directory, and nothing outside it.

The locationURI of an import is a path below root, and a relative one is resolved against the document that has it as RFC 3986 says (5.2): the importer is read from sub/a.dmn (its base_uri), so lib.dmn is sub/lib.dmn and ../lib.dmn is lib.dmn. The first model is read from the root itself unless Dmn.load/2 was given the base_uri it was read from, below the root. The file is returned with the path that is below root as its URI ({:ok, bytes, "sub/lib.dmn"}: dot segments are removed, and the same file is always the same string).

It is refused ({:error, :outside_root}) if the result goes up out of the root (../lib.dmn from a model at the root), is absolute, is a URL (it has a scheme), starts with ~, has a backslash, names a file through a link (any component, the file itself included, that is a symbolic link), or names something that is not a regular file, or that has a second name (a hard link inside the root to a file outside it is a file outside it as well). A file that is larger than max_bytes (Dmn.Limits) is not read ({:error, :too_large}). An import with no locationURI is not found.

Options: :limits.

map(models)

@spec map(%{optional(String.t()) => binary()}) :: t()

A resolver from a map of namespaces to the bytes of the model that has that namespace, or of locations to bytes. A model is looked up by its locationURI first, then by its namespace.

iex> resolver = Dmn.Resolver.map(%{"urn:b" => "<definitions/>"})
iex> resolver.(%Dmn.Model.Import{name: "b", namespace: "urn:b", import_type: "x"}, %Dmn.Model{name: "a", namespace: "urn:a"})
{:ok, "<definitions/>"}
iex> resolver.(%Dmn.Model.Import{name: "c", namespace: "urn:c", import_type: "x"}, %Dmn.Model{name: "a", namespace: "urn:a"})
{:error, :not_found}