- MCP server with stdio transport for local use - Search episodes, transcripts, hosts, and series - 4,511 episodes with metadata and transcripts - Data loader with in-memory JSON storage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
222 lines
19 KiB
Plaintext
222 lines
19 KiB
Plaintext
Episode: 3392
|
|
Title: HPR3392: Structured error reporting
|
|
Source: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr3392/hpr3392.mp3
|
|
Transcribed: 2025-10-24 22:35:56
|
|
|
|
---
|
|
|
|
This is Hacker Public Radio Episode 3392-4 Tuesday, the 3rd of August 2021.
|
|
Tid's show is entitled, Structured Air Reporting, and is part of the series' Haskell it is hosted
|
|
by Tukuta Oto, and is about 29 minutes long, and carries an explicit flag.
|
|
The summary is two turtle talks about how she improved build times, by breaking down air
|
|
reporting to smaller parts.
|
|
This episode of HPR is brought to you by archive.org.
|
|
Support universal access to all knowledge by heading over to archive.org forward slash donate.
|
|
Hello, this is Tula Turto, and you are listening to the Hacker Public Radio.
|
|
Today's episode is about air reporting.
|
|
In Episode 2020, I talked a little bit about validation in Haskell, and then I have been working on
|
|
that a little bit more, and improved how the errors are now reported.
|
|
So this episode will sort of continue from that episode.
|
|
So, Tukuta when I wrote the error reporting, I had a, well, I basically wanted to have a unified
|
|
way of reporting errors, so that when the server sends an error report to the client,
|
|
it's always in the same format, it's an adjacent response that has a machine-readable code
|
|
for the client process, and then it would have some human-readable message that can be shown
|
|
to the end user. And how I originally wrote that I had a one-type error code that basically
|
|
enumerated all the possible error cases that could happen on the server side.
|
|
By the way, a lot of the code I'm talking about in this episode will be in the show notes as
|
|
usual, so that error code had long list of possible errors that could happen on the server side.
|
|
And then I had four health functions, error code to status code, which takes error code and returns
|
|
in there. And this one is for designing what HTTB status code the servers would be returning.
|
|
So, for example, if there's a, for example, the client's, in example, if the client tries to
|
|
upgrade the intrigue, stats of the person in the game, that defines how good that person is
|
|
in the intrigue. And if that upgraded value is too low, then this, we would be raising the,
|
|
stat is too low error code. And the error code to status code would turn that to the 400,
|
|
so that would indicate that it's a tag request. Another function, status code to text,
|
|
takes this integral status code and returns the byte string. This is for the HTTB response again.
|
|
So, for the 400, it would return a tag request. And then the actual need of the problem,
|
|
error code to text. This takes the error code and returns a textual representation of that,
|
|
something that can be shown to the end user. And in this case, it would be stat intrigue is too low.
|
|
And then the final function is racist errors. This takes a list of error code and returns handler
|
|
for app unit. This is, this is the thing that the handler function, that's the function that
|
|
is handling the user request calls when it wants to raise an error. If the, if that list contains
|
|
any error codes, then it uses the, these three functions I mentioned earlier and construct HTTB
|
|
response. So in our case, it would be HTTB 400, that request, that would have a body response
|
|
body of JSON, that will contain an array, that will contain an object, that would have a
|
|
code element containing another object that has a tag status to flow and content in rig. This is
|
|
machine-readable part. And then it will have an error element that contains stat intrigue is too low,
|
|
text, this is the part that gets shown to the user. And this works pretty fine. I was
|
|
happy how this worked. But this has a problem. All the, all the possible errors are enumerated in one
|
|
type. So that type will grow longer and longer and longer. And every part of the system that wants
|
|
to report an error is referring to that type. Meaning whenever I add a new error to the enumeration,
|
|
then all the places that refer to that type, that depend on that type, will have to be compiled.
|
|
And of course, that triggers a cascading effect because all the modules that refer to those
|
|
compiled ones need to be compiled and so on. So the picket type grows and the more places it is
|
|
referred, the slower the compile time will be because it triggers quite a big compilation.
|
|
And I didn't like that. So I wanted a solution and thought that I could break that error code type
|
|
into smaller types. So instead of having one huge error, I would have a, for example, person
|
|
creation error type that would list only the errors that would happen when you are creating a new
|
|
person. And then there would be an account error that would list all the errors that relate to
|
|
the account. And these types will be defined in different modules close to, close to
|
|
place where they used, used. Which means that if I add a new person creation error,
|
|
then only that module will be compiled and all the modules that depend on that module.
|
|
So there's still a compilation coin on, but there's a lot less compilation coin on.
|
|
But here's a problem. But I cannot have a, I cannot have a list that contains two different types
|
|
in it. Like that. Race if errors takes a list of error code. And if I have multiple types,
|
|
I cannot, I cannot, I cannot put multiple different types of values of multiple different types
|
|
in the same, the list always has to contain the same, same type. But I decided that I worried
|
|
that a little bit later. First, I wanted to split the huge error code type into the smaller types.
|
|
So I defined person creation error that is at just a numberation of all the errors relating to
|
|
the person creation. And then use the template Haskell to derive to JSON and from JSON functions
|
|
for this specific type, these are, these functions can be used to serialize and then serialize
|
|
person creation error to and from JSON, which is what I'm sending to sending and receiving from the
|
|
client. And then I wanted to have a way of getting the HTTP status code and that error description.
|
|
Previously, there were defined as functions that took the error code. But now I have to work on
|
|
multiple types. So answer was to define a type class. Type class is sort of kind of similar
|
|
than interface in object-oriented programs, but slightly different. Because in object-oriented
|
|
world, when you define an interface, you define a set of functions or methods that you have to
|
|
implement when you implement that interface in Haskell, that part is the similar. But in the,
|
|
for example, in the sheet shop, when you define a class that implements an interface, you have to
|
|
implement those functions and methods when you are defining the class. In Haskell world,
|
|
when you are defining a data type, you're not actually telling that this has that specific type
|
|
class. You are doing that separately. So you can have a data type and then you can, in addition to
|
|
that definition, you can define the type class instances for that. Which means that if I come up with a
|
|
completely new type class that I need, I can define an instance of that for the already existing
|
|
types. I can, for example, take the Boolean type and say that here's the instance of my type class
|
|
for the Boolean. So I'm adding new functionality that can be used to handle the Boolean
|
|
without having to modify the Boolean. That's the big difference.
|
|
In any way, anyway, so I defined a type class called error code class A. And the definition
|
|
goes like a class, error code class A, where HDTV status code, a double colon A, a row int,
|
|
description, double colon A, a row text. This means that any, any instance of error code class
|
|
has to have two functions defined. HDTV status codes, that takes that data type A and turns that
|
|
into the int. And then there has to be the description function that takes that data type A
|
|
and turns that into text. And these are the ones that I'm using to
|
|
turning the person creation error into a HDTV status code and to the user-readable message.
|
|
That status code to text that I mentioned earlier, that takes the status code and turns that into
|
|
the HDTV message, like that request, for example, that I could use as is. I didn't have to modify that
|
|
because that takes an int, returns a binary, yeah, byte string. Sorry.
|
|
Okay, so to define the instance of error code class, I just wrote wrote instance error code class,
|
|
person creation error, where HDTV status code equals slash case, status to low,
|
|
a row 400 and anywhere at all the cases. So HDTV status code is a case analysis basically.
|
|
So if it's a, if the, if the error, person creation error is the status to low,
|
|
then I'm returning 400. If I have a could not confirm data of third, then that's 500.
|
|
That's a, the internal server error, there's something really fishy going on in case if a
|
|
person doesn't have a date of third. And then the description is again a,
|
|
a description equals slash case, status to low, s, a row, status, plus s, plus is to low.
|
|
This is the textual, textual, that's the name of the status, that is to low. So when,
|
|
when I'm raising a status to low error somewhere, I'm creating a status to low error somewhere,
|
|
I have to, I have to tell what status to low, is it intrigue or is it a
|
|
diplomacy or is it a martial skill or something else. So I have to tell that this specific
|
|
status to low and then it's reported to the user that, hey, by the way, you try to do something
|
|
funny or the program try to do something funny and this status to low, somebody has to fix it.
|
|
So, and of course, since I had multiple types of different conscious error, I had to
|
|
define this error code class instance to all of those types. It's pretty mechanical work.
|
|
You just list all the error cases that you, you can have decide, but HTTP status, they should
|
|
result to and what textual representation to the user should be given.
|
|
But then the bigger error, so I need to, way to put these different kinds of error into same list,
|
|
and that's, that's not something that Haskell can do in Haskell. A list is always
|
|
the regular list is always of all the values inside of it has to be of same type.
|
|
So, if they have to be a same type, I thought that maybe I could sort of have a helper type and
|
|
grab these specific specific types into that type. So, I define the new type as data E code A
|
|
equals E code A. So, this is a, this defines a type E code that can, that has a one constructor,
|
|
also called E code that takes one parameter A. So, it can wrap anything.
|
|
And, but the problem here is that the resulting, resulting type is a E code A. So,
|
|
if I'm using it to wrap a person creation error, the resulting type is E code
|
|
person creation error. And if I use it wrap common error, then I, the resulting type is E code
|
|
common error. And these are different types. So, I need to, I need to, I need it a way that I could
|
|
wrap type values of types. And somehow lose the type of the wrapped value in a way that the
|
|
resulting, resulting type would always be the same. So, and then of course, the another thing is
|
|
that I need that the previous definition that had only the data E code A.
|
|
It doesn't have, well, it can be anything. It could be indexer, it could be a boolean,
|
|
it could be a some map or any type. So, I cannot, I cannot, I cannot, I cannot turn any type
|
|
into a error report. I have to, I, all the data that I wrap needs to, needs to have a
|
|
instance for the error report class. And also, I need to have an instance for the two JSON. So,
|
|
that I can serialize them to the JSON data and send them to the, send them back to the client.
|
|
There's several ways of doing this, but I chose to use general, generalized algebraic data types
|
|
against for the short. That's an extension that has to be turned on. That's not in the standard
|
|
Haskell. So, I had to have a pragma language cards in the top of the file. And that allowed me
|
|
to define the E code as a following data, E code, where E code, double colon, error code class A,
|
|
comma, JSON, A, tick, arrow, A, arrow, E code. So, what that, what that means is that, that defines a
|
|
type E code that has a one constructor, also called E code, that constructor takes a one
|
|
parameter A and returns a E code. And that A is constrained. It has to have an instance for the
|
|
error code class and to JSON. So, it has constraints, which is what we wanted. And also,
|
|
the resulting type is E code, not E code A. So, we essentially have, with this constructor,
|
|
we can wrap suitable types, resulting types will be E code. So, we have lost the type
|
|
information there. We know that the value that has been wrapped, it has instances to the error
|
|
code class and to JSON. So, we can still use functions from those type classes. But that's
|
|
all we need to do with that. So, we still need that A to turn this E code into an error message.
|
|
But that, that is, that is, that was easy because everything we wrapped with the E code has
|
|
error code class type class defined with. So, I think I could just define
|
|
instance error code class E code. So, I'm defining an instance of error code class for the E code,
|
|
where the status code is just the, is HTTP status code E R. Sorry, HTTP, HTTP status code R and
|
|
description is description R. A, meaning that the, when you are asking HTTP status code from the
|
|
E code, it's the HTTP status code of the fractal value and description of the E code is the
|
|
description of the fractal value. And then I wrote an instance for the, of the JSON E code.
|
|
So, turning this E code into JSON. So, I can serialize it to the client. I wrote that instance
|
|
by hand. I could have derived that automatically, but I wanted to have a final control.
|
|
I wanted to decide by myself how, what, what is the format and structs of the JSON.
|
|
The code, code is on the, on the show notes, but basically it's just the serialized, when,
|
|
when you are serializing E code, you are creating a JSON object that has three fields, HTTP code,
|
|
that is that status code, fault code that is, that fractal value turned into JSON.
|
|
So, in case we have a, just on, for example, error, first name is empty, then that first name is
|
|
empty, because it doesn't have any parameters, it's just turned into the string. So, fault code will be
|
|
the first name is empty. In case, it's a status tool, that takes a, where the constructor takes
|
|
parameters, then the fault code will be an object that has a tag, status tool load and condensed
|
|
in trig. Tag is the constructor that was used and condensed is the, all the values that were
|
|
touched to that constructor. That is the machine readable part and the error description is the
|
|
description A, that's the user readable part in our case, starting trig is too low.
|
|
And now I could just define the racist errors in terms of the list of E code,
|
|
returning handler for app unit, meaning this is a function that you can call from the handler
|
|
function and it doesn't return any values, but if it, if, if the, if the errors, it will send a
|
|
status JSON. The code is on the, on the show notes, but basically, first we check if the list contains
|
|
any errors to begin with and if it does, then we take the first element of the list,
|
|
call HTTP status code function to that, to get the status code. And if for some reason,
|
|
even if we checked for the, for the condensed, condensed of the list, if for some reason it would be
|
|
end here, then we would be using 500 internal server error. The message is the status code to text
|
|
function called with the, that code. So, for example, a factory quest or internal server error.
|
|
And then the, we would call send the status JSON, give it the status, with the code and the message,
|
|
and body of errors turned into the JSON. And this, this works pretty, pretty neatly. So,
|
|
then since all the errors, so, so this use, this uses E code, as a, as a parameter. So,
|
|
I cannot give a status to low value to here. I have to wrap it to the E code. So, I have to
|
|
basically call it E code, lower status to low, quotation, start in a intrigue, quotation mark.
|
|
And having to manually wrap everything to the E code, it's bit tedious.
|
|
So, I define the helper function, status to low, that takes a text and returns an E code.
|
|
And that basically is just status to low s equals E code, lower status to low s. So,
|
|
it doesn't contain much logic at all. It just takes that string that you give to it.
|
|
It uses that to create a status to low value and wraps that value into E code.
|
|
So, that just makes things a little bit easier for you to less typing, basically. It
|
|
saves a little bit of spiking. And another thing is, if I at some point in the future,
|
|
sorry, if I, in some point of the future, decided that I want to change how the internal,
|
|
internal of this error reporting works. I can just change the internals and then update all the
|
|
functions that I'm using, using to create these error values. And hopefully, whatever these
|
|
functions are called, don't need to change at all. So, a little bit of future proof in here,
|
|
but the main idea is that I'm just, I just wanted to save a little bit of typing. And this works
|
|
pretty well. I get a faster compilation times. I got, I get the same error messages on the client
|
|
side as before. And this is nice. There's a different solution to this problem, right?
|
|
Instead of having those wrapping, wrapping everything into the E code and having all those
|
|
different, different types that you use to create those errors, I could have just defined one
|
|
record code, E code that would have filled HD deeper code that is in and description that is text.
|
|
So, instead of having a typed errors, I would, I would just use record. Whenever I would want to
|
|
report an error, I would just create an, I would just create a new E code record,
|
|
put a suitable HD deep HD deep code and suitable description on it. And that would, that would
|
|
work just fine. The drawback with that approach would have been that I would not have any control
|
|
over what gets reported. I could have a HD deep code 1, 2, 3, 4 and description something funny
|
|
happened. Or I could make a typo. Somewhere I would be reporting a, I wanted to report with
|
|
error code 400 and I could mistype that action to 499. And compiler wouldn't be able to catch that.
|
|
Of course, I could still have a helper function that is to low, that takes a parameter of text,
|
|
that returns an E code and instead of that, status to, status to low function, construct this E code
|
|
record. But I just basically decided to record with the first, first option when I was thinking
|
|
about this, there isn't that, there isn't that big difference in the end result. Actually the
|
|
end result is the same. It's just a little bit, little bit difference in the internals of the
|
|
machinery of the things work. Okay, that's, that's it. So this is, this is how I, I
|
|
broke one huge error code type into, into smaller types and improved the compilation times
|
|
of my project. If you have any questions, comments or feedback, I can be reached with the email
|
|
or at theferiverse, where I am, todurdoattech.lgpt. Have a happy hacking.
|
|
You've been listening to HackerPublicRadio at HackerPublicRadio.org. We are a community podcast
|
|
network that releases shows every weekday Monday through Friday. Today's show, like all our shows,
|
|
was contributed by an HBR listener like yourself. If you ever thought of recording a podcast
|
|
then click on our contributing to find out how easy it really is. HackerPublicRadio was founded by
|
|
the digital dog pound and the infonomicon computer club and it's part of the binary revolution at
|
|
binwreff.com. If you have comments on today's show, please email the host directly, leave a comment
|
|
on the website or record a follow-up episode yourself. Unless otherwise status, today's show is
|
|
released on the earth. Create a comments, attribution, share a light, 3.0 license.
|