Skip to content

Conversation

@oleiade
Copy link
Contributor

@oleiade oleiade commented Dec 4, 2025

What?

This pull request introduces a new mechanism/API for surfacing structured, JS-visible error types from Go modules in k6, enabling proper JavaScript instanceof checks and richer error handling for users.

This is probably not the right way to do it, but it has the benefit of demonstrating the target UX and feature, and open the discussion on how we might implement this

The changes include new helper types and functions for error construction, conversion, and export. It also, for demonstration purposes only refactors the fs module to use these new error types that can be defined.

This is by no mean meant to be production-ready, it might even need to live in sobek itself, but this feature has been discussed and requested internally many times, and I just wanted to kickoff the implementation. Let's make it better together 🤝

Example

Implementing a custom error type

Define your custom error structure(s) in your go module definition::

const fsErrorConstructorName = "FSError"

type FSError {
    // Embedding the JSError in your type is what will make being handled as
    // expected by the JS runtime.
	*common.JSError

	// kind contains the kind of error that has occurred. This is specific to the fs module, and
	// it could be any field you want, although, note that JSError also has a `Properties` map property
	// for these use cases.
	kind errorKind
}

// newFSError creates a new Error object of the provided kind and with the
// provided message.
func newFSError(k errorKind, message string) *fsError {
	return &FSError{
		JSError: common.NewJSError(common.JSErrorConfig{
			Constructor: fsErrorConstructorName,
			Name:        k.String(),
			Message:     message,
		}),
		kind: k,
	}
}

Export the type during the module exports phase:

// Exports implements the modules.Module interface and returns the exports of
// our module.
func (mi *ModuleInstance) Exports() modules.Exports {
	return modules.Exports{
		Named: map[string]any{
			"open": mi.Open,
			"SeekMode": map[string]any{
				"Start":   SeekModeStart,
				"Current": SeekModeCurrent,
				"End":     SeekModeEnd,
			},
			"FSError": common.ExportNamedError(fsErrorConstructorName)(mi.vu.Runtime()),
		},
	}
}

Using the new custom type

import { operation, FSError } from 'k6/experimental/fs';

export default async function () {
    try {
        await operation("it's supposed to fail");
    } catch (err) {
        if (err instanceof FSError) {
            console.log("THIS IS THE WAY!");
        } else {
            console.log("not what we expected...");
        }
        console.log(JSON.stringify(err))
    }
}

@oleiade oleiade self-assigned this Dec 4, 2025
@oleiade oleiade temporarily deployed to azure-trusted-signing December 4, 2025 09:25 — with GitHub Actions Inactive
@oleiade oleiade temporarily deployed to azure-trusted-signing December 4, 2025 09:26 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant