customrules

package module
v0.0.0-...-32d0beb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2026 License: MIT Imports: 19 Imported by: 1

Documentation

Overview

Package customrules provides support for writing OpenAPI linter rules in TypeScript/JavaScript.

This package enables users to extend the OpenAPI linter with custom rules written in TypeScript or JavaScript, without modifying the core linter code. Rules are transpiled using esbuild and executed in a goja JavaScript runtime.

Usage

Import this package for its side effects to enable custom rule loading:

import (
    _ "github.com/speakeasy-api/openapi/openapi/linter/customrules"
    "github.com/speakeasy-api/openapi/openapi/linter"
)

func main() {
    config := &linter.Config{
        Extends: []string{"recommended"},
        CustomRules: &linter.CustomRulesConfig{
            Paths: []string{"./rules/*.ts"},
        },
    }
    lint, err := linter.NewLinter(config)
    // ...
}

Writing Custom Rules

Custom rules are written in TypeScript using the @speakeasy-api/openapi-linter-types package for type definitions. Rules can extend the Rule base class or implement the RuleRunner interface.

Example rule (require-summary.ts):

import { Rule, registerRule, createValidationError } from '@speakeasy-api/openapi-linter-types';
import type { Context, DocumentInfo, RuleConfig, ValidationError } from '@speakeasy-api/openapi-linter-types';

class RequireOperationSummary extends Rule {
  id(): string { return 'custom-require-operation-summary'; }
  category(): string { return 'style'; }
  description(): string { return 'All operations must have a summary.'; }
  summary(): string { return 'Operations must have summary'; }

  run(ctx: Context, docInfo: DocumentInfo, config: RuleConfig): ValidationError[] {
    const errors: ValidationError[] = [];

    for (const opNode of docInfo.index.operations) {
      const op = opNode.node;
      if (!op.getSummary()) {
        errors.push(createValidationError(
          config.getSeverity(this.defaultSeverity()),
          this.id(),
          'Operation is missing a summary',
          op.getRootNode()
        ));
      }
    }

    return errors;
  }
}

registerRule(new RequireOperationSummary());

Configuration

Custom rules are configured in the linter configuration file (lint.yaml):

extends: recommended

custom_rules:
  paths:
    - ./rules/*.ts
    - ./rules/security/*.ts
  timeout: 30s  # Optional timeout per rule (default: 30s)

rules:
  - id: custom-require-operation-summary
    severity: error

API Access

Custom rules have access to the full document structure through the DocumentInfo object:

  • docInfo.document - The parsed OpenAPI document
  • docInfo.location - The file path or URL of the document
  • docInfo.index - Pre-computed index with collections for efficient iteration:
  • index.operations - All operations in the document
  • index.componentSchemas - All component schemas
  • index.inlineSchemas - All inline schemas
  • And many more collections...

Field and method names are lowercased in JavaScript (e.g., getSummary(), not GetSummary()) due to goja's UncapFieldNameMapper.

Error Handling

Runtime errors in custom rules (exceptions, timeouts) are caught and returned as validation errors. Source maps are used to map error locations back to the original TypeScript source.

Thread Safety

Each rule file gets its own goja runtime instance. Goja runtimes are not thread-safe, so rules should not share state between executions. The Loader maintains a cache of transpiled code but creates new runtimes for each load.

Limitations

  • No npm package resolution - rules must be self-contained or use the types package
  • No file system access from JavaScript
  • No network access from JavaScript
  • Memory and CPU limits should be configured via the timeout setting

Index

Constants

View Source
const DefaultTimeout = 30 * time.Second

DefaultTimeout is the default execution timeout for custom rules.

Variables

This section is empty.

Functions

func ExtractInlineSourceMap

func ExtractInlineSourceMap(code string) (*sourcemap.Consumer, error)

ExtractInlineSourceMap extracts and parses an inline source map from JavaScript code.

func SetDefaultConfig

func SetDefaultConfig(config *Config)

SetDefaultConfig sets the default config for the loader. This allows customizing the loader with programmatic options (like Logger). Call this before creating any linters to take effect. This function is thread-safe.

Types

type BridgedContext

type BridgedContext struct {
	// contains filtered or unexported fields
}

BridgedContext wraps a Go context for JavaScript access.

func NewBridgedContext

func NewBridgedContext(ctx context.Context) *BridgedContext

NewBridgedContext creates a JavaScript-accessible context wrapper.

func (*BridgedContext) Deadline

func (bc *BridgedContext) Deadline() any

Deadline returns the deadline in milliseconds since epoch, or undefined if no deadline.

func (*BridgedContext) IsCancelled

func (bc *BridgedContext) IsCancelled() bool

IsCancelled returns true if the context has been cancelled.

type Config

type Config struct {
	// Paths are glob patterns for rule files (e.g., "./rules/*.ts")
	Paths []string `yaml:"paths,omitempty" json:"paths,omitempty"`

	// Timeout is the maximum execution time per rule (default: 30s)
	Timeout time.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`

	// Logger is used for console output from rules (programmatic only, not from YAML)
	Logger Logger `yaml:"-" json:"-"`
}

Config configures custom rule loading.

func (*Config) GetLogger

func (c *Config) GetLogger() Logger

GetLogger returns the configured logger or the default.

func (*Config) GetTimeout

func (c *Config) GetTimeout() time.Duration

GetTimeout returns the configured timeout or the default.

type CustomRule

type CustomRule struct {
	// contains filtered or unexported fields
}

CustomRule wraps a JavaScript rule object and implements RuleRunner.

func NewCustomRule

func NewCustomRule(rt *Runtime, jsRule goja.Value, sourceFile string, sm *sourcemap.Consumer, config *Config) (*CustomRule, error)

NewCustomRule creates a new CustomRule from a JavaScript rule object.

func (*CustomRule) Category

func (r *CustomRule) Category() string

func (*CustomRule) DefaultSeverity

func (r *CustomRule) DefaultSeverity() validation.Severity

func (*CustomRule) Description

func (r *CustomRule) Description() string

func (*CustomRule) ID

func (r *CustomRule) ID() string
func (r *CustomRule) Link() string

func (*CustomRule) Run

func (r *CustomRule) Run(ctx context.Context, docInfo *linter.DocumentInfo[*openapi.OpenAPI], config *linter.RuleConfig) []error

Run executes the JavaScript rule against the document.

func (*CustomRule) Summary

func (r *CustomRule) Summary() string

func (*CustomRule) Versions

func (r *CustomRule) Versions() []string

type JSFix

type JSFix struct {
	// contains filtered or unexported fields
}

JSFix bridges a JavaScript fix object to the Go Fix interface.

func (*JSFix) Apply

func (f *JSFix) Apply(doc any) error

func (*JSFix) Description

func (f *JSFix) Description() string

func (*JSFix) Interactive

func (f *JSFix) Interactive() bool

func (*JSFix) Prompts

func (f *JSFix) Prompts() []validation.Prompt

func (*JSFix) SetInput

func (f *JSFix) SetInput(responses []string) error

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader loads custom rules from TypeScript/JavaScript files.

func NewLoader

func NewLoader(config *Config) *Loader

NewLoader creates a new custom rule loader.

func (*Loader) LoadRules

func (l *Loader) LoadRules(baseConfig *baseLinter.CustomRulesConfig) ([]baseLinter.RuleRunner[*openapi.OpenAPI], error)

LoadRules loads all custom rules from the configured paths.

type Logger

type Logger interface {
	Log(args ...any)
	Warn(args ...any)
	Error(args ...any)
}

Logger is the interface for custom rule console output.

type MappedError

type MappedError struct {
	Original   error
	SourceFile string
	Line       int
	Column     int
	Message    string
}

MappedError wraps an error with original source location.

func MapException

func MapException(exc *goja.Exception, sourceFile string, sm *sourcemap.Consumer) *MappedError

MapException maps a goja exception to original TypeScript source locations.

func (*MappedError) Error

func (e *MappedError) Error() string

func (*MappedError) Unwrap

func (e *MappedError) Unwrap() error

type Runtime

type Runtime struct {
	// contains filtered or unexported fields
}

Runtime wraps a goja JavaScript runtime with custom rule support. Each Runtime instance is NOT thread-safe and should only be used from a single goroutine.

func NewRuntime

func NewRuntime(logger Logger, config *Config) (*Runtime, error)

NewRuntime creates a new JavaScript runtime configured for custom rules.

func (*Runtime) CallMethod

func (rt *Runtime) CallMethod(obj goja.Value, method string, args ...goja.Value) (goja.Value, error)

CallMethod calls a method on a JavaScript object.

func (*Runtime) ClearInterrupt

func (rt *Runtime) ClearInterrupt()

ClearInterrupt clears any pending interrupt.

func (*Runtime) ClearRegisteredRules

func (rt *Runtime) ClearRegisteredRules()

ClearRegisteredRules clears the registered rules list.

func (*Runtime) GetRegisteredRules

func (rt *Runtime) GetRegisteredRules() []goja.Value

GetRegisteredRules returns all rules registered via registerRule().

func (*Runtime) Interrupt

func (rt *Runtime) Interrupt(reason string)

Interrupt interrupts the currently running JavaScript. This is used for timeout handling.

func (*Runtime) RunScript

func (rt *Runtime) RunScript(name, code string) (goja.Value, error)

RunScript executes JavaScript code in the runtime.

func (*Runtime) ToValue

func (rt *Runtime) ToValue(v any) goja.Value

ToValue converts a Go value to a goja value.

type TranspiledRule

type TranspiledRule struct {
	SourceFile string
	Code       string
	SourceMap  *sourcemap.Consumer
}

TranspiledRule holds transpiled JavaScript code and its source map.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL