View Source Spark.Dsl.Extension behaviour (spark v2.2.3)
An extension to the Spark DSL.
This allows configuring custom DSL components, whose configurations can then be read back. This guide is still a work in progress, but should serve as a decent example of what is possible. Open issues on Github if you have any issues/something is unclear.
The example at the bottom shows how you might build a (not very contextually relevant) DSL extension that would be used like so:
defmodule MyApp.Vehicle do
use Spark.Dsl
end
defmodule MyApp.MyResource do
use MyApp.Vehicle,
extensions: [MyApp.CarExtension]
cars do
car :ford, :focus, trim: :sedan
car :toyota, :corolla
end
end
The extension:
defmodule MyApp.CarExtension do
@car_schema [
make: [
type: :atom,
required: true,
doc: "The make of the car"
],
model: [
type: :atom,
required: true,
doc: "The model of the car"
],
type: [
type: :atom,
required: true,
doc: "The type of the car",
default: :sedan
]
]
@car %Spark.Dsl.Entity{
name: :car,
describe: "Adds a car",
examples: [
"car :ford, :focus"
],
target: MyApp.Car,
args: [:make, :model],
schema: @car_schema
}
@cars %Spark.Dsl.Section{
name: :cars, # The DSL constructor will be `cars`
describe: """
Configure what cars are available.
More, deeper explanation. Always have a short one liner explanation,
an empty line, and then a longer explanation.
""",
entities: [
@car # See `Spark.Dsl.Entity` docs
],
schema: [
default_manufacturer: [
type: :atom,
doc: "The default manufacturer"
]
]
}
use Spark.Dsl.Extension, sections: [@cars]
end
Often, we will need to do complex validation/validate based on the configuration
of other resources. Due to the nature of building compile time DSLs, there are
many restrictions around that process. To support these complex use cases, extensions
can include transformers
which can validate/transform the DSL state after all basic
sections/entities have been created. See Spark.Dsl.Transformer
for more information.
Transformers are provided as an option to use
, like so:
use Spark.Dsl.Extension, sections: [@cars], transformers: [
MyApp.Transformers.ValidateNoOverlappingMakesAndModels
]
By default, the generated modules will have names like __MODULE__.SectionName.EntityName
, and that could
potentially conflict with modules you are defining, so you can specify the module_prefix
option, which would allow
you to prefix the modules with something like __MODULE__.Dsl
, so that the module path generated might be something like
__MODULE__.Dsl.SectionName.EntityName
, and you could then have the entity struct be __MODULE__.SectionName.EntityName
without conflicts.
To expose the configuration of your DSL, define functions that use the
helpers like get_entities/2
and get_opt/3
. For example:
defmodule MyApp.Cars do
def cars(resource) do
Spark.Dsl.Extension.get_entities(resource, [:cars])
end
end
MyApp.Cars.cars(MyResource)
# [%MyApp.Car{...}, %MyApp.Car{...}]
See the documentation for Spark.Dsl.Section
and Spark.Dsl.Entity
for more information
Summary
Functions
Get the entities configured for a given section
Get an option value for a section at a given path.
Get a value that was persisted while transforming or compiling the resource, e.g :primary_key
Types
@type t() :: module()
Callbacks
Functions
Get the entities configured for a given section
get_opt(resource, path, value, default \\ nil, configurable? \\ false)
View SourceGet an option value for a section at a given path.
Checks to see if it has been overridden via configuration.
Get a value that was persisted while transforming or compiling the resource, e.g :primary_key