WORD!, PATH!, and URL!s as ERROR! ids

R3-Alpha gave all the built in errors categories and a WORD! for an id. These were established in the %errors.r file:

https://github.com/rebol/rebol/blob/master/src/boot/errors.r

Each category had an integer "base" number, and the errors were numbered according to that. Adding and removing errors would disrupt these numbers... so the idea was presumably that these would settle over time. At some point of stability, errors removed from the middle would not have their numbers reused, while new errors would be added at the end of their groups.

But the usage of integers for error codes isn't very "ecology friendly". I question the wisdom of even having error numbers in Rebol...IDs may collide, but numbers will do so even quicker. A lot of places you might imagine they'd be useful they aren't really; for instance, exit status codes for processes on UNIX are limited to bytes...and even in that limited range they use conventions other than Rebol's numbers.

To mitigate collisions, I've wondered if errors might be based on some kind of hierarchy, in which you could choose to refer to errors by WORD!, PATH!, or URL!.

error.id ~= 'missing-delimiter
error.id ~= 'syntax/missing-delimiter
error.id ~= http://rebol.info/e/syntax/missing-delimiter

So the idea being that the error IDs would be fully qualified somehow, but when you trapped them you could use a level of specificity that met your particular desires for rigor. If you were worried there was some other kind of 'missing-delimiter error that could be thrown affecting your particular code, you might use a full URL. I made up the ~= operator here as something you might use which would gloss this difference.

This isn't really a concrete proposal...just pointing out that I don't think numbering errors has a lot of point. While the names have changed here and there, the numbers have thus far had basically no meaning. It seems to me any future-forward strategy would avoid inventing error numbers entirely (an error of course might include a number as part of its parameterization if it's referring to a non-Rebol system, e.g. HTTP error codes).

So if we killed the numbering, would anyone notice?

I used to check error numbers as it's easier than checking for literal text.

I wonder if FIND/LAST/MATCH is the "operator" I'm suggesting here.

find/last/match error.id '/missing-delimiter
find/last/match error.id '/syntax/missing-delimiter
error.id = http://rebol.info/e/syntax/missing-delimiter

This modifies to suggest REFINEMENT!, REFINEMENT!-headed PATH!s, or URL! for ERROR! ids.

The reason I suggest REFINEMENT! vs. WORD! is so you don't wind up doing id: http://rebol.info/e/syntax/missing-bad-delimiter then find/last/match id 'bad-delimiter and get a false positive.

It needs to keep getting better... one idea I had was that each module would define its own specialization of FAIL which would identify itself, and would chain the error IDs:

 Rebol [
    Name: "My Module"
    Type: 'Module
    Errors: https://example.com/modules/my-module/e/
 ]

 fail '/invalid-foo [foo: 10]

The idea would be that this module's FAIL would be defined to merge that onto the Errors: URL. So you'd get an error with id https://example.com/modules/my-module/e/invalid-foo.

What you'd want to do would be at that URL to serve a page that had english readable text as HTML, but also some kind of template that could be scraped out and used by the console to pretty print the error in a brief notation. If there was no web server or it were down (or taking too long to reply to a request), you'd just see the error as having the ID and any parameters.

I've wrestled with the question of how to merge all these desires together. e.g. what if you want to implicate a value and give it an ID, then you've got multiple things going on. Or what if the block wants to provide text as a template as well as variables? I make some notes about this in the source:

; Ultimately we might like FAIL to use some clever error-creating dialect
; when passed a block, maybe something like:
;
;     fail [<invalid-key> {The key} key-name: key {is invalid}]
;
; That could provide an error ID, the format message, and the values to
; plug into the slots to make the message...which could be extracted from
; the error if captured (e.g. error/id and `error/key-name`.  Another
; option would be something like:
;
;     fail/with [{The key} :key-name {is invalid}] [key-name: key]

The time to start thinking about how to make all this elegant is now. We have more tools than ever!

1 Like

Just to clarify, the data/content stored at: https://example.com/modules/my-module/e/invalid-foo is created by and hosted by whom -- the author of the script? Or is this URL to Ren-C project standard error messages (and links to glossary, help, docs, forum etc.)? And a script author could modify the target of that URL to point to their own custom/branded messages?

Even if I'm off on the above, I like that you're thinking about this and thinking of ways of making error messaging a better experience. In a similar vein I've been pondering a dialect to be included within code comments to generate developer To-Do lists or diaries, requirements/use-cases, or to integrate/link to architectural diagrams/flows, stuff like that.

At some point I'd also like to be able to use script metadata to auto-generate a browsable/searchable code-library (like rebol.org's script library, but more lightweight and customizable), so I can better organize, curate, and query my hundreds of scripts for reusable code. This should be even easier to achieve today with the Beta/One enhancements.

If your module is part of the standard Ren-C universe, then your root URL provided here should be to such a path. If it's not, then you (or your turnkey error-server provider) provides it.

This is kind of where back in the day I thought the rebol.org vs. rebol.net distinction would come in handy. The .net would redirect subdomains for people, so rebmu.rebol.net would just forward off to wherever that was hosted. If it became important to the community but went down at some point, the subdomain could be retaken and then managed by someone else.

The idea would be that whatever that page happened to serve up, the console could sniff out a formatting template from it. There could be more formal methods, a meta item in a http header telling you exactly how to find it, with fallbacks that were a kind of web scraping.

Workflow

It's just about being able to write in your https module simple things like:

fail '/not-found [code: 404 url: location]

Which uses a FAIL specialization/adaptation that tacks the module's error URL base onto it. So if the user traps the error they get something like:

>> site: https://example.com/catsurmifuble!!  ; Note: inside joke
>> trap [data: read site]
== make error! [
    id: https://rebol.org/https/e/not-found
    code: 404
    url: https://example.com/catsurmifuble!!
 ]

But if the error gets raised in the console, the console tries to do better than this. It hits fetches URL, and one way or another scrapes out a template something like:

 ["URL was not found:" url]

Perhaps with language-specific responses on that. Which could just be managed by conventional web requests as opposed to coming up with a fancier negotiation system in Rebol itself, e.g. by the Accept-Language sent to the server. Not only would the server be configured to give back readable text in that language, but also have the right version of the template: ["URL no encontrada:" url]

Hence the if you got the error reported (vs trapped) it could say:

>> data: read site
** Error: URL was not found: https://example.com/catsurmifuble!!

The console could hold onto the last error. Then it would know the ID if you said something like why:

>> why
Launching browser: https://rebol.org/https/e/not-found

That's the kind of flow I'm envisioning here. The code itself reports errors by ID and with their parameters, and you delegate to the web itself for the pretty printing and translation. Worst case fallback you just get the error molded.

Yep, gotcha. Yes, I like it. Maybe introduce the feature barebones at first to see how it's received and collect suggestions/feedback. I think there are really good possibilities which could stem from this approach, particularly for newbies.

1 Like

Something to be mindful of is that this is the kind of feature that an intern at a software company could spend all summer on as their sole task, and not necessarily get it right.

Plus, the whole concept or model of what an ERROR! is, where that "flavor" of type goes, what its implications are...that was never worked out in R3-Alpha. Red has no better answers, and is still putting numeric IDs that are Rebol specific in the errors:

red> print mold try [1 / 0]
make error! [
    code: 400
    type: 'math
    id: 'zero-divide
    arg1: none
    arg2: none
    arg3: none
    near: none
    where: '/
    stack: 12296944
]

The meanings aren't questioned, the "type" isn't questioned (what's it for?) The 3-arg limit, fixed at the names arg1, arg2, arg3...also not questioned.

Trying to get something like this redesigned for Beta/One is too ambitious. So what has to happen is that a few things get pinned down. I'm starting with "error IDs are URLs" and "there's some lightweight comparison operator you can use to check just the last part of an ID".

>> error: trap [data: read site]
== make error! [
    id: https://rebol.org/https/e/not-found
    code: 404  ; user parameter to this error type, NOT generic "error code"
    url: https://example.com/catsurmifuble!!
 ]

 >> error.id ~= '/not-found
 == #[true]

Something along these lines. And I'm trying to think about how to push things like the where and near into the meta information, so they don't wind up affecting error comparisons but are still available.

(This raises the question of using something like id which the user might want for a named field as part of the error. Maybe they're just out of luck with that.)

But we need to get the most bang for the buck, committing to important things that aren't planned to change.

1 Like