# AshOutstanding
[](https://hex.pm/packages/ash_outstanding)
[](https://hexdocs.pm/ash_outstanding/)
[](https://github.com/diffo-dev/ash_outstanding/blob/master/LICENSE.md)
Ash Extension for implementing `Outstanding` protocol on Ash.Resource and Ash.TypedStruct.
Implementing Outstanding on your Ash Resources and/or TypedStructs is useful when you have an expected / actual twin, and want to establish whether/which expectations are outstanding given actual.
This is a powerful concept, particularly as expectations are declarations of intent, and we want to separate the concerns of whether we've sufficiently met our expectations from how to deal with what is outstanding. Ash itself is highly declarative, creating and exploiting Spark DSL.
This extension employs Spark DSL to allow you to declare how your Ash Resources and TypedStructs should implement Outstanding.
## Installation
Add to the deps:
```elixir
def deps do
[
{:ash_outstanding, "~> 0.2.2"},
]
end
```
## Tutorial
To get started you need a running instance of [Livebook](https://livebook.dev/)
[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fdiffo%2Ddev%2Fash%5Foutstanding%2Fblob%2Fdev%2Fash%5Foutstanding%5Fextension.livemd)
## Usage
Add `AshOutstanding.Resource` to `extensions` list within `use Ash.Resource` options:
```elixir
defmodule Example.Resource do
use Ash.Resource,
extensions: [AshOutstanding.Resource]
end
```
### Configuration
Generally you will want to configure your Ash Resource so that outstanding?(expected, actual) is true when the essentials of your Ash Resource are satisfied. What this really means will be specific to each resource in your domain.
By defaults this includes public fields which aren't sensitive. This includes attributes, calculations and aggregates.
```elixir
defmodule Specification.Resource do
use Ash.Resource,
extensions: [AshOutstanding.Resource]
end
```
You have more control using the expect keyword.
- expect, provide list of Ash Record fields which can have have expectations, or a behaviour configuration map.
Here is an example `outstanding` dsl section, which configures a Specification resource so that we can set expectations on any or all of the values of keys :name, :major_version and :version while ignoring other fields in the expected/actual resource.
When nil_outstanding?(expected, actual) is true, outstanding(expected, actual) returns nil
When nil_outstanding?(expected, actual) is false, outstanding(expected, actual) returns a struct of your Ash Record with just the unmet expectations.
```elixir
defmodule Specification.Resource do
use Ash.Resource,
extensions: [AshOutstanding.Resource]
outstanding do
expect [:name, :major_version, :version]
end
end
```
The behaviour configuration map options are:
* private? - Whenever to expect private fields (defaults false) - Ash.Resource only
* sensitive? - Whenever to pick sensitive fields (defaults false) - Ash.Resource only
* include - Fields to expect. In addition to public?: true && sensitive?: false fields.
* exclude - Fields not to expect.
```elixir
outstanding do
# Expect only listed fields
expect [:included_field_a, :included_field_b]
# Expect all fields including public?: false and sensitive?: true
expect %{private?: true, sensitive?: true}
# Expect default fields with specific inclusions and exclusions
expect %{include: [:included_private_field], exclude: [:excluded_public_field]}
end
```
## Using Outstanding on your resource
We don't need to set all of the expectations, by default a missing expectation or explict nil means we have no expectation (so nothing will be outstanding)
In the following example we require the access specification to be major version 2. We don't match version as we aren't concerned with minor or trival versions.
```elixir
use Outstand
expected = %Specification.Resource{name: access, major_version: 2}
actual = %Specification.Resource{name: access, major_version: 1}
expected >>> actual
false
expected --- actual
%Specification.Resource{major_version: 2}
```
If we are happy with either major_version: 1 or 2, we can use a range
```elixir
use Outstand
expected = %Specification.Resource{name: access, major_version: 1..2}
actual = %Specification.Resource{name: access, major_version: 1}
expected >>> actual
true
expected --- actual
nil
```
Outstanding supports regex, here we use a regex to expect version: is at least v1.1
We don't need an expectation on major version.
```elixir
use Outstand
expected = %Specification.Resource{name: access, version: ~r/v1.1/}
actual = %Specification.Resource{name: access, version: v1.1.17}
expected >>> actual
true
expected --- actual
nil
```
Outstanding supports expected functions. Arity 1 and 2 expected functions use actual as a parameter. Arity 2 expected functions take an argument list, and this can be a list of 'prototype' expected resources used in an Outstand or your own expected function.
You may need to ensure your expected/actual Ash Resources are appropriately loaded, depending on what key/values you expect.
The __meta__ field is nil expectation.
## Customize
A dsl option is provided which will insert a custom arity 3 function into the outstanding pipeline.
Below customize is used to ensure that outstanding is enriched with expected.id
```elixir
defmodule Specification.Resource do
use Ash.Resource,
extensions: [AshOutstanding.Resource]
outstanding do
expect [:name, :major_version, :version]
customize fn outstanding, expected, _actual ->
case outstanding do
nil ->
outstanding
%_{} ->
outstanding
|> Map.put(:id, expected.id)
end
end
end
end
```
## Using Outstanding in Ash Expressions
Ash Expressions can call outstanding(expected, actual) and is_outstanding(expected, actual) via custom expressions. This is particularly useful when combined with relationships, as a supervising resource can manage its expectations of a supervised resource.
AshOutstanding includes the Outstanding and IsOutstanding custom expressions for the Ash.DataLayer.Simple, Ash.DataLayer.ETS, [AshCsv.DataLayer](https://github.com/ash-project/ash_csv) and [AshNeo4j.DataLayer](https://github.com/diffo-dev/ash_neo4j)
To make these available add to your config.exs:
```elixir
config :ash, :custom_expressions, [AshOutstanding.Expressions.Outstanding, AshOutstanding.Expressions.IsOutstanding]
```
## Typed Structs
AshOutstanding includes support for [Ash.TypedStruct](https://hexdocs.pm/ash/Ash.TypedStruct.html). The DSL for TypedStruct is identical to Resource, except that 'expect' is simpler since TypedStruct fields cannot be private or sensitive.
```elixir
defmodule Specification.TypedStruct do
use Ash.TypedStruct,
extensions: [AshOutstanding.TypedStruct]
outstanding do
expect [:name, :major_version, :version]
end
end
```
## Acknowledgements
Thanks to [Telstra](https://www.telstra.com.au/) for supporting innovation in orchestration and inventory shared-tech which resulted in the award winning difference engine [2024 TMF Excellence Award in Autonomous Networks](https://www.tmforum.org/about/awards-and-recognition/excellence-awards/winners-2024/) powering three network service entities enabling outstanding product experience [2025 TMF Excellence Award in Customer Experience](https://www.tmforum.org/about/awards-and-recognition/excellence-awards/winners-2025/) and inspiring both this open source and internal shared-tech.
Thanks to [Dmitry Maganov](https://github.com/vonagam) for [ash_jason](https://github.com/vonagam/ash_jason) which was an exemplar.
Kudos to the [Ash Core](https://github.com/ash-project) for [ash](https://github.com/ash-project/ash) 🚀
## Links
[Diffo.dev](https://www.diffo.dev)
[`Outstanding` protocol docs](https://hexdocs.pm/outstanding/)
[`Outstanding` protocol livebook] (https://livebook.dev/run/?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Foutstanding%2Fblob%2Fdev%2Foutstanding.livemd)