<p>Equality of structured errors</p>
<h2 id="background">Background</h2>
<p>In previous episode, I built a system where error codes weren't defined in one huge type. This made compilation times faster and maintenance quite a bit more easier.</p>
<h2 id="problem">Problem</h2>
<p>I wanted to write a test to see that parameters passed to <code>validatePatchApiPersonR</code> are validated correctly. <code>patchApiPersonR</code> is used by client to do partial updates on a <code>Person</code> entity. There's three different cases I wanted to check:</p>
<ul>
<li>trying to change life focus too soon (there's 5 year cooldown)</li>
<li>trying to select same life focus that has already been selected</li>
<li>trying to modify somebody else's avatar</li>
</ul>
<p>Code is shown below and the last 2 lines are the interesting ones. There I'm using equality to compare if a given error exists in a list of errors created by <code>validatePatchApiPersonR</code>.</p>
<pre><code>spec :: Spec
spec = do
    describe &quot;people&quot; $ do
        describe &quot;life focus&quot; $ do
            describe &quot;Validating requests&quot; $ do
                it &quot;All errors are reported&quot; $ do
                    forAll anyCompletelyFaultyRequest $
                        \(userE, personE, msg, date) -&gt;
                            let res = validatePatchApiPersonR (userE ^. entityKeyL, userE ^. entityValL, personE, msg, date)
                                newFocus = msg ^? patchPersonRequestLifeFocus . _Just . _Just
                            in
                                case res of
                                    V.Success _ -&gt;
                                        expectationFailure &quot;Invalid request was not detected&quot;

                                    V.Failure errs -&gt; do
                                        errs `shouldSatisfy` (\xs -&gt; any (\x -&gt; &quot;CanNotChangeLifeFocusSoSoon&quot; `isInfixOf` (pack $ show x)) xs)
                                        errs `shouldContain` [ canNotReselectSameLifeFocus newFocus ]
                                        errs `shouldContain` [ insufficientRights ]
</code></pre>
<h2 id="detour-on-equality">Detour on equality</h2>
<p>Equality in Haskell works slightly differently compared to for example C#. There is no built in, default implementation that gets used when the programmer hasn't written their own. If you want to compare equality, there needs to be implementation specific to your data types. This is done by making an instance of type class <code>Eq</code> (<a href="https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Eq.html">https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Eq.html</a>).</p>
<pre><code>class Eq a where
  (==) :: a -&gt; a -&gt; Bool
  (/=) :: a -&gt; a -&gt; Bool
</code></pre>
<p>There's two functions: <code>==</code> for equality and <code>/=</code> for inequality. You need to implement either one.</p>
<h2 id="back-to-problem">Back to problem</h2>
<p><code>ECode</code> is our structured error code type and defined as follows (this is short recap of previous episode):</p>
<pre><code>data ECode where
    ECode :: (ErrorCodeClass a, ToJSON a, Eq a, Show a) =&gt; a -&gt; ECode
</code></pre>
<p>It can wrap anything that has correct type class instances and you will always get <code>ECode</code> as a result. It hides the specific type of thing being wrapped and only functions defined in type classes are available.</p>
<h2 id="first-try">First try</h2>
<p>Peel away <code>ECode</code> and compare what's inside and compare wrapped values:</p>
<pre><code>instance Eq ECode where
    ECode a == ECode b =
        a == b
</code></pre>
<p>This will lead into a error "Couldn't match expected type &#x2018;a&#x2019; with actual type &#x2018;a1&#x2019;. &#x2018;a1&#x2019; is a rigid type variable bound by a pattern with constructor...". This is because <code>ECode</code> can wrap many different types, there is no quarantee that you're comparing values of same type. The whole error is show below for reference:</p>
<pre><code>[35 of 76] Compiling Errors           ( src/Errors.hs, .stack-work/dist/x86_64-linux/Cabal-2.2.0.1/build/Errors.o )

/home/tuula/programming/sky/src/Errors.hs:148:14: error:
    &#x2022; Couldn&#39;t match expected type &#x2018;a&#x2019; with actual type &#x2018;a1&#x2019;
      &#x2018;a1&#x2019; is a rigid type variable bound by
        a pattern with constructor:
          ECode :: forall a.
                   (ErrorCodeClass a, ToJSON a, Eq a, Show a) =&gt;
                   a -&gt; ECode,
        in an equation for &#x2018;==&#x2019;
        at src/Errors.hs:147:16-22
      &#x2018;a&#x2019; is a rigid type variable bound by
        a pattern with constructor:
          ECode :: forall a.
                   (ErrorCodeClass a, ToJSON a, Eq a, Show a) =&gt;
                   a -&gt; ECode,
        in an equation for &#x2018;==&#x2019;
        at src/Errors.hs:147:5-11
    &#x2022; In the second argument of &#x2018;(==)&#x2019;, namely &#x2018;b&#x2019;
      In the expression: a == b
      In an equation for &#x2018;==&#x2019;: ECode a == ECode b = a == b
    &#x2022; Relevant bindings include
        b :: a1 (bound at src/Errors.hs:147:22)
        a :: a (bound at src/Errors.hs:147:11)
    |
148 |         a == b
    |
</code></pre>
<h2 id="second-try">Second try</h2>
<p>We can use <code>Show</code> to turn <code>ECode</code> into string and compare them. This is what I did initially.</p>
<pre><code>instance Eq ECode where
    a == b = show a == show b
</code></pre>
<p>While this works, it feels hacky. It relies on string representations being different. If you accidentally write <code>Show</code> instance in a way that produces same string with two different values, the comparison breaks down.</p>
<h2 id="third-time-is-a-charm">Third time is a charm</h2>
<p>After pondering a bit, I asked myself a question "When are two <code>ECode</code> equal?". The answer I arrived is "When they have same http status code and description." Then I could write yet different take on equality:</p>
<pre><code>instance Eq ECode where
    a == b =
        httpStatusCode a == httpStatusCode b
        &amp;&amp;  description a == description b
</code></pre>
<p>This states that to two <code>ECode</code> values are equal, if they have same <code>httpStatusCode</code> and <code>description</code>.</p>
<p>Thanks for listening, if you have any questions or comments, you can reach me via email or in fediverse, where I'm <code>Tuula@tech.lgbt</code>. Or even better, you could record your own hacker public radio episode.</p>
<p>Ad astra!</p>
