Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline

### Features
* enhanced List implementation
* fixed Duration and related expected functions not to use to_timeout
* fixed Duration and related expected functions not to use to_timeout

## [v0.2.2](https://github.com/diffo-dev/outstanding/compare/v0.2.1...v0.2.2) (2025-05-19)

### Features
* deriving callback for Structs
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,30 @@ iex> Outstanding.outstanding(:no_value, "a")

## Implementing Outstanding for other types

The defoutstanding macro can be used to implement outstanding on other types, including your own structs.
This requires some though as to what it means to 'resolve' your expected type with actual.

This requires some though as to what it means to 'resolve' your expected struct with actual. The following XYZ struct uses Outstanding on the map to resolve :x, :y, :z, and also expects that actual is also an XYZ struct, although you may want to allow matching using straight maps or other struct with equivalent fields.
## derive for Structs
Outstanding implements the ```__deriving__/3``` callback so you can simply derive an Outstanding implementation when you define your struct. By default this performs outstanding on all fields, and requires the actual struct to be of the same type.

```elixir
defmodule ABC do
@derive Outstanding
defstruct [:a, :b, :c]
end
```

You can also exclude fields with the ```except``` option

```elixir
defmodule AB do
@derive {Outstanding, except: [:c]}
defstruct [:a, :b, :c]
end
```

## defoutstanding macro

More flexibly, the defoutstanding macro can be used to implement outstanding on other types, including your own structs.

```elixir
use Outstand
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.1
0.2.2
34 changes: 34 additions & 0 deletions lib/outstanding.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,38 @@ defprotocol Outstanding do
"""
@spec outstanding?(t, any()) :: boolean()
def outstanding?(expected, actual)

@impl true
defmacro __deriving__(module, options) do
quote do
defimpl Outstanding, for: unquote(module) do
import Outstand, only: [map_to_struct: 2, outstanding?: 1]
def outstanding(expected, actual) do
case {expected, actual} do
{nil, nil} ->
nil

{_, ^expected} ->
nil

{%name{}, %name{}} ->
expected
|> Map.from_struct()
|> Map.drop(Keyword.get(unquote(options), :except, []))
|> Outstanding.outstanding(Map.from_struct(actual))
|> Outstand.map_to_struct(name)

{%name{}, _} ->
expected
|> Map.from_struct()
|> Map.drop(Keyword.get(unquote(options), :except, []))
|> Outstand.map_to_struct(name)
end
end
def outstanding?(expected, actual) do
Outstand.outstanding?(outstanding(expected, actual))
end
end
end
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Outstanding.MixProject do
use Mix.Project

@name :outstanding
@version "0.2.1"
@version "0.2.2"
@description "Elixir protocol calculating outstanding from expected and actual"
@github_url "https://github.com/diffo-dev/outstanding"

Expand Down
21 changes: 18 additions & 3 deletions outstanding.livemd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Outstanding Elixir Protocol

```elixir
Mix.install([{:outstanding, "~> 0.2.1"}], consolidate_protocols: false)
Mix.install([{:outstanding, "~> 0.2.2"}], consolidate_protocols: false)
```

## Overview
Expand Down Expand Up @@ -195,11 +195,26 @@ Now resolve the second child by setting its actual ```state: :active``` and ```s

## How to implement Outstanding for your Types And Structs

You can implement outstanding for any Type or Struct using the Outstand defoutstanding macro.
Expected is of whatever type you are implementing the protocol for, and actual must be of Any type.
You can easily implement Outstandign on Structs and other Types

### Derive Outstanding on your Structs

When you define your struct you can simply @derive the Outstanding protocol. By default this expects all fields, and actual must be a struct of the same type.

```elixir
defmodule ABC do
@derive Outstanding
defstruct [:a, :b, :c]
end

outstanding(%ABC{a: "apple", b: "banana", c: "carrot"}, %ABC{a: "apple", b: "bagel", c: "cake"})
```

### Outstanding on any Type

You can implement outstanding for any Type or Struct using the Outstand defoutstanding macro.
Expected is of whatever type you are implementing the protocol for, and actual must be of Any type.

The following is the Outstanding implementation for Regex, which expects actual to match the (evaluated) regex.
We won't evaluate this as it is already defined.

Expand Down
19 changes: 19 additions & 0 deletions test/struct_derive_except_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Outstanding.StructDeriveExceptTest do
use ExUnit.Case
use Outstand

@v0 :value0
@v1 :value1

defmodule AB do
@derive {Outstanding, except: [:c]}
defstruct [:a, :b, :c]
end

gen_something_outstanding_test("key outstanding", %AB{a: @v0, b: @v1}, %AB{a: @v0})
gen_something_outstanding_test("value outstanding", %AB{a: @v0, b: @v1}, %AB{a: @v1, b: @v1})
gen_nothing_outstanding_test("realized", %AB{a: @v0, b: @v1}, %AB{a: @v0, b: @v1})
gen_nothing_outstanding_test("realized, extra item", %AB{a: @v0, b: @v1}, %AB{a: @v0, b: @v1, c: @v1})
gen_result_outstanding_test("key result", %AB{a: @v0, b: @v1}, %AB{a: @v0}, %AB{b: @v1})
gen_result_outstanding_test("value result", %AB{a: @v0, b: @v1}, %AB{a: @v1, b: @v1}, %AB{a: @v0})
end
22 changes: 22 additions & 0 deletions test/struct_derive_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Outstanding.StructDeriveTest do
use ExUnit.Case
use Outstand

@v0 :value0
@v1 :value1
@v2 :value2

defmodule ABC do
@derive Outstanding
defstruct [:a, :b, :c]
end

gen_something_outstanding_test("key outstanding", %ABC{a: @v0, b: @v1}, %ABC{a: @v0, c: @v1})
gen_something_outstanding_test("value outstanding", %ABC{a: @v0, b: @v1, c: @v2}, %ABC{a: @v1, b: @v1, c: @v2})
gen_nothing_outstanding_test("realized", %ABC{a: @v0, b: @v1, c: @v2}, %ABC{a: @v0, b: @v1, c: @v2})
gen_nothing_outstanding_test("realized, no c expectation", %ABC{a: @v0, b: @v1}, %ABC{a: @v0, b: @v1})
gen_nothing_outstanding_test("realized, nil c expectation", %ABC{a: @v0, b: @v1, c: nil}, %ABC{a: @v0, b: @v1})
gen_nothing_outstanding_test("realized, extra item", %ABC{a: @v0, b: @v1}, %ABC{a: @v0, b: @v1, c: @v1})
gen_result_outstanding_test("key result", %ABC{a: @v0, b: @v1}, %ABC{a: @v0, c: @v1}, %ABC{b: @v1})
gen_result_outstanding_test("value result", %ABC{a: @v0, b: @v1}, %ABC{a: @v1, b: @v1}, %ABC{a: @v0})
end