Dmn.Imports (dmn v0.1.0)

Copy Markdown View Source

Reads the models that a model imports, all of them, through the resolver the caller gave (Dmn.Resolver).

Each import that is a DMN model (importType is a namespace of the model, DMN 1.6, 6.3.3) is resolved, and the bytes are read with Dmn.load/2, under the same limits as the first model, so an imported model is as untrusted as the one that names it. Then its own imports are read, to the depth max_import_depth, and no more than max_models models in all. The namespace of a model has to be the one the import says, since the namespace is what its elements are referred to by.

The order. The imports are read breadth first: those of the root before those of the models they bring. A namespace is read once, and the first import of it decides which document is the model of that namespace, so it is the root that decides what its own lib is, and not a model that it imports and that names lib at another location.

The identity of a model. The elements of a model are referred to by its namespace, so there is one model for a namespace in a compile, and a locationURI is only a hint that the resolver turns into a document. It is a URI reference (DMN 1.6, 13.3.2), and a relative one is resolved against the URI of the document that has it: two models in two directories that both name lib.dmn can mean two files, so the same text does not make the same document, and the resolver is given the importer to tell them apart (Dmn.Model.base_uri is where the resolver said the importer was read from, Dmn.Resolver). Two imports of a namespace are the same model if the documents are the same bytes, and are a conflict (:import_conflict, an error) if they are not: the later import is not used, as if the model could not be had, and the model that has it gets nothing in its place (what it reads from it is :unresolved_import, and its types are not defined). An import that names no location accepts the model that the namespace has. Every import that names one is asked for and compared (within the budget below) unless this importer asked for that location before. The first model is a model of its namespace like the others when it was loaded from bytes (Dmn.load/2 keeps their hash as Dmn.Model.sha256): a model that imports it back, at a location, is given it if that is the same bytes, and a conflict if it is not. A first model that was not loaded has no bytes to tell by, and an import of its namespace is accepted. Which document each model is (the text it was first asked for by, the URI the resolver said it is at, if it did, and the SHA-256 of its bytes) is in the result, and Dmn.Compiled.imports has it, for a caller who wants to pin the models it uses (the first model is not an import, and is not in it).

The budget of the whole graph. One document is limited by Dmn.Limits (bytes, depth, elements), and the graph of imports as a whole has a budget of its own, since a root can name tens of thousands of imports and every one that fails would otherwise cost a read: max_import_reads documents asked for (whatever comes of them), max_import_bytes bytes in all, and import_timeout of time, which a document is read within what is left of it. An import that passes one is a diagnostic (:import_limit) and is not read. A document is asked for once for each model that names it and each location (or each namespace, for an import with no location), whether or not it could be read, so the same import that fails twice is not paid for twice.

What cannot be had is a diagnostic of the importing model (:import_not_resolved, :import_invalid, :import_namespace_mismatch, :import_conflict, :import_limit), and the model is compiled without it. An import that is not a DMN model (a schema, a PMML file) is not read, and is said to be so (:import_not_dmn, an info).

Summary

Types

Which document a model is: the text of the locationURI it was first asked for by (nil if the import named no location), the URI the resolver said it is at (nil if the resolver did not say), and the SHA-256 of its bytes.

Functions

The models by namespace, the first one included, the diagnostics, and the identity of each model that was read.

Types

identity()

@type identity() :: %{
  location: String.t() | nil,
  uri: String.t() | nil,
  sha256: String.t()
}

Which document a model is: the text of the locationURI it was first asked for by (nil if the import named no location), the URI the resolver said it is at (nil if the resolver did not say), and the SHA-256 of its bytes.

Functions

load(root, resolver, limits)

@spec load(Dmn.Model.t(), Dmn.Resolver.t() | nil, Dmn.Limits.t()) ::
  {%{optional(String.t()) => Dmn.Model.t()}, [Dmn.Diagnostic.t()],
   %{optional(String.t()) => identity()}}

The models by namespace, the first one included, the diagnostics, and the identity of each model that was read.

resolver is a Dmn.Resolver.t/0 or nil: with none, every import is a diagnostic. In the models, a model has no import that was refused as a conflict, and a model that was read has the base_uri the resolver said.