pluggo

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2025 License: MIT Imports: 17 Imported by: 0

README

🔌 Pluggo

A simple HTTP-based plugin system for Go.

Build Status GoDoc Go Report Card GitHub release

🚀 Features

  • 🚀 Simple Plugin System: Easy-to-use API for creating and managing plugins
  • 🔒 Type Safety: Generic-based functions with compile-time type checking
  • 🌐 HTTP Communication: Reliable plugin communication over HTTP
  • 📋 Schema Validation: Automatic JSON schema generation and validation
  • 🔄 Dynamic Loading: Load and execute plugins at runtime
  • Performance: Minimal overhead with efficient execution
  • 🏥 Health Checks: Built-in health monitoring for plugin processes

📦 Installation

go get github.com/henomis/pluggo

🎯 Quick Start

Creating a Plugin

Create a plugin that implements a simple greeting function:

plugin/plugin.go

package main

import (
    "context"
    "github.com/henomis/pluggo"
)

type Input struct {
    Name string `json:"name" jsonschema:"minLength=3"`
}

type Output struct {
    Greeting string `json:"greeting"`
}

func Hello(ctx context.Context, in *Input) (*Output, error) {
    return &Output{Greeting: "Hello, " + in.Name + "!"}, nil
}

func main() {
    p := pluggo.NewPlugin()
    
    // Create validator for input validation
    v, err := pluggo.NewValidator(&Input{})
    if err != nil {
        panic(err)
    }
    
    // Register the function
    p.AddFunction("hello", pluggo.NewFunctionHandler(Hello, v).Handler())
    
    // Start the plugin server
    err = p.Start()
    if err != nil {
        panic(err)
    }
}

Build the plugin:

go build -o plugin plugin.go
Using the Plugin

main.go

package main

import (
    "context"
    "fmt"
    "github.com/henomis/pluggo"
)

type Input struct {
    Name string `json:"name"`
}

type Output struct {
    Greeting string `json:"greeting"`
}

func main() {
    // Create client and load plugin
    client := pluggo.New("./plugin/plugin")
    err := client.Open(context.Background())
    if err != nil {
        panic(err)
    }
    defer client.Close()
    
    // Create type-safe function
    hello := pluggo.NewFunction[Input, Output]("hello", client.Connection())
    
    // Call the function
    result, err := hello.Call(&Input{Name: "World"})
    if err != nil {
        panic(err)
    }
    
    fmt.Println(result.Greeting) // Output: Hello, World!
}

📚 Examples

The repository includes several examples in the examples/ directory

🏗️ Architecture

Pluggo uses an HTTP-based architecture where:

  1. 🚀 Plugin Launch: Client launches plugin as separate process
  2. 📡 HTTP Communication: Plugin starts HTTP server and communicates port via stdout
  3. 🔍 Discovery: Client discovers available functions via /_schemas endpoint
  4. 🏥 Health Monitoring: Built-in health checks via /_healthz endpoint
  5. ⚡ Function Execution: Type-safe function calls via HTTP POST requests
┌─────────────┐    HTTP     ┌─────────────┐
│   Client    │◄───────────►│   Plugin    │
│             │             │             │
│ ┌─────────┐ │             │ ┌─────────┐ │
│ │Function │ │             │ │Handler  │ │
│ │         │ │────────────►│ │         │ │
│ └─────────┘ │             │ └─────────┘ │
└─────────────┘             └─────────────┘

🛠️ API Reference

Plugin Server
Creating a Plugin
p := pluggo.NewPlugin()
Adding Functions
p.AddFunction(name string, handler http.HandlerFunc)
Starting the Server
err := p.Start()
Client
Creating a Client
client := pluggo.New(pluginPath string)
Opening Connection
err := client.Open(ctx context.Context)
Getting Available Functions
schemas, err := client.Schemas()
Type-Safe Functions
Creating a Function
fn := pluggo.NewFunction[InputType, OutputType](name string, connection *pluggo.Connection)
Calling a Function
result, err := fn.Call(input *InputType)
Getting Function Schema
schema, err := fn.Schema()

🛡️ Input Validation

Pluggo supports automatic input validation using JSON Schema tags:

type Input struct {
    Name  string `json:"name" jsonschema:"minLength=3,maxLength=50"`
    Age   int    `json:"age" jsonschema:"minimum=0,maximum=120"`
    Email string `json:"email" jsonschema:"format=email"`
}

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built with Go's native HTTP server for maximum performance
  • Uses JSON Schema for robust input validation
  • Inspired by modern microservices architecture patterns

Made with ❤️ by henomis

Documentation

Overview

Package pluggo provides a framework for creating and communicating with plugin systems using HTTP-based communication. It allows for dynamic loading and execution of plugins as separate processes that communicate over HTTP.

The package consists of two main components: - Client: for launching and communicating with plugins - Plugin: for creating plugins that can be launched by the client

Plugins are executable files that start an HTTP server and communicate their port number to the client via stdout. The client then uses HTTP requests to execute functions within the plugin.

Index

Constants

View Source
const (

	// DefaultFunctionExecutionTimeout is the HTTP timeout for requests the launcher makes to the plugin (health + exec)
	DefaultFunctionExecutionTimeout = 2 * time.Minute
	// DefaultHealthCheckTimeout is the total time the launcher will wait for the plugin to become healthy
	DefaultHealthCheckTimeout = 5 * time.Second
	// DefaultHealthCheckInterval defines how often to retry hitting /_healthz while waiting
	DefaultHealthCheckInterval = 150 * time.Millisecond
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

Client manages the lifecycle and communication with a plugin process. It handles launching the plugin executable, establishing HTTP communication, health checking, and graceful shutdown.

func New

func New(path string, opts ...ClientOption) *Client

New creates a new Client instance with the specified plugin path and optional configuration. The path should point to an executable file that implements the plugin protocol. Options can be provided to customize timeouts and other behavior.

func (*Client) Close

func (c *Client) Close() error

Close gracefully shuts down the plugin process and cleans up resources. It cancels the plugin's context and kills the process if it's still running. This method is safe to call multiple times.

func (*Client) Connection

func (c *Client) Connection() *Connection

Connection returns the current HTTP connection details for the plugin. Returns nil if the plugin is not currently running or connected.

func (*Client) HealthCheck

func (c *Client) HealthCheck() <-chan struct{}

create a function that return a channel to the user and start a goroutine that periodically check the health endpoint of the plugin. If the plugin becomes unhealthy it closes the channel to notify the user.

func (*Client) Open

func (c *Client) Open(ctx context.Context) error

Open launches the plugin process and establishes communication. It performs the following steps: 1. Validates that the plugin file exists and is executable 2. Starts the plugin process 3. Reads the HTTP port from the plugin's stdout 4. Establishes HTTP connection and waits for the plugin to become healthy

Returns an error if any step fails. The plugin process will be terminated automatically if initialization fails.

func (*Client) Schemas

func (c *Client) Schemas() (Schemas, error)

Schemas retrieves the list of available functions and their input/output schemas from the plugin. This provides introspection capabilities to understand what functions are available and their expected data structures.

type ClientOption

type ClientOption func(*Client)

ClientOption is a function that configures a Client during creation.

func WithFunctionExecutionTimeout

func WithFunctionExecutionTimeout(timeout time.Duration) ClientOption

WithFunctionExecutionTimeout sets the HTTP request timeout for function execution calls.

func WithHealthCheckInterval

func WithHealthCheckInterval(interval time.Duration) ClientOption

WithHealthCheckInterval sets the interval between health check attempts during plugin startup.

func WithHealthCheckTimeout

func WithHealthCheckTimeout(timeout time.Duration) ClientOption

WithHealthCheckTimeout sets the total timeout duration for waiting for the plugin to become healthy.

func WithHeartbeatInterval

func WithHeartbeatInterval(interval time.Duration) ClientOption

WithHeartbeatInterval sets the interval between heartbeat checks for the plugin.

type Connection

type Connection struct {
	FunctionExecutionTimeout time.Duration
	BaseURL                  string
}

Connection represents an active HTTP connection to a plugin server. It contains the base URL and configuration for communication with the plugin.

type Function

type Function[T, R any] struct {
	// contains filtered or unexported fields
}

Function represents a typed function that can be called on a remote plugin. T is the input type and R is the output type for the function. It handles JSON serialization/deserialization and HTTP communication automatically.

func NewFunction

func NewFunction[T, R any](name string, clientConnection *Connection) *Function[T, R]

NewFunction creates a new typed function client for calling a specific function on a plugin. The function will serialize input of type T to JSON, send it to the plugin, and deserialize the response into type R.

func (*Function[T, R]) Call

func (f *Function[T, R]) Call(input *T) (*R, error)

Call executes the function with the provided input and returns the result. The input is serialized to JSON, sent to the plugin via HTTP POST, and the response is deserialized back to the expected output type.

func (*Function[T, R]) Name

func (f *Function[T, R]) Name() string

Name returns the name of this function as registered with the plugin.

func (*Function[T, R]) Schema

func (f *Function[T, R]) Schema() (*Schema, error)

Schema retrieves the JSON schema definition for this function's input and output types. This provides introspection capabilities to understand the expected data structure.

func (*Function[T, R]) SetTimeout

func (f *Function[T, R]) SetTimeout(timeout time.Duration)

SetTimeout configures the HTTP timeout for this specific function. This overrides the default timeout set in the connection.

type FunctionExecutionError

type FunctionExecutionError struct {
	Function string
	Err      error
}

FunctionExecutionError is returned when there's an error executing a function within a plugin.

func (*FunctionExecutionError) Error

func (e *FunctionExecutionError) Error() string

Error implements the error interface for FunctionExecutionError.

type FunctionHandler

type FunctionHandler[T, R any] struct {
	// contains filtered or unexported fields
}

FunctionHandler wraps a user-provided function with HTTP handling capabilities. It provides automatic JSON serialization/deserialization, input validation, and schema generation for plugin functions.

func NewFunctionHandler

func NewFunctionHandler[T, R any](fn func(context.Context, *T) (*R, error), validator *Validator[T]) *FunctionHandler[T, R]

NewFunctionHandler creates a new function handler that wraps a user function with HTTP request/response handling, JSON processing, and optional input validation. The handler automatically generates JSON schemas for input and output types.

func (*FunctionHandler[T, R]) Handler

func (m *FunctionHandler[T, R]) Handler() *Handler

Handler returns the underlying HTTP handler and schema information. This is used internally by the plugin framework to register the function.

type FunctionListError

type FunctionListError struct {
	Err error
}

FunctionListError is returned when there's an error retrieving the list of available functions from a plugin.

func (*FunctionListError) Error

func (e *FunctionListError) Error() string

Error implements the error interface for FunctionListError.

type FunctionLookupError

type FunctionLookupError struct {
	Function string
	Err      error
}

FunctionLookupError is returned when there's an error looking up or accessing a specific function.

func (*FunctionLookupError) Error

func (e *FunctionLookupError) Error() string

Error implements the error interface for FunctionLookupError.

type FunctionNotFoundError

type FunctionNotFoundError struct {
	Function string
}

FunctionNotFoundError is returned when attempting to call a function that doesn't exist in the plugin.

func (*FunctionNotFoundError) Error

func (e *FunctionNotFoundError) Error() string

Error implements the error interface for FunctionNotFoundError.

type Handler

type Handler struct {
	HTTPHandler http.Handler
	Schema      Schema
}

Handler contains the HTTP handler and schema information for a plugin function.

type Plugin

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

Plugin represents a plugin server that can host multiple functions. It manages the HTTP server, function registration, and provides health check and schema introspection endpoints.

func NewPlugin

func NewPlugin() *Plugin

NewPlugin creates a new plugin instance with default configuration. It sets up the HTTP server, logging, health check endpoint, and schema endpoint.

func (*Plugin) AddFunction

func (l *Plugin) AddFunction(functionName string, handler *Handler)

AddFunction registers a new function with the plugin server. The function becomes available at the endpoint /{functionName} and its schema at /{functionName}/_schemas. Function names are validated to ensure they contain only safe characters.

func (*Plugin) Start

func (l *Plugin) Start() error

Start begins serving the plugin on an ephemeral port. The port number is printed to stdout as the first line, which allows the client to discover how to connect to the plugin. This method blocks until the server stops or encounters an error.

func (*Plugin) Stop

func (l *Plugin) Stop()

Stop gracefully shuts down the plugin server and cleans up resources. This method is safe to call multiple times.

type PluginExecutionError

type PluginExecutionError struct {
	Err error
}

PluginExecutionError is returned when there's an error starting, running, or communicating with a plugin.

func (*PluginExecutionError) Error

func (e *PluginExecutionError) Error() string

Error implements the error interface for PluginExecutionError.

type PluginNotFoundError

type PluginNotFoundError struct {
	Err error
}

PluginNotFoundError is returned when the specified plugin file cannot be found or accessed.

func (*PluginNotFoundError) Error

func (e *PluginNotFoundError) Error() string

Error implements the error interface for PluginNotFoundError.

type Schema

type Schema struct {
	Input  map[string]any `json:"input"`
	Output map[string]any `json:"output"`
}

Schema represents the input and output JSON schemas for a plugin function. This provides introspection capabilities for clients to understand the expected data structures.

type Schemas

type Schemas map[string]Schema

Schemas is a map of function names to their corresponding schemas.

type Validator

type Validator[T any] struct {
	// contains filtered or unexported fields
}

Validator provides JSON schema validation for input data. It uses a compiled JSON schema to validate incoming data against the expected structure before deserialization.

func NewValidator

func NewValidator[T any](v *T) (*Validator[T], error)

NewValidator creates a new validator for type T by generating a JSON schema from the provided struct. The validator can then be used to validate JSON input before deserialization.

func (*Validator[T]) Validate

func (v *Validator[T]) Validate(data any) *jsonschema.EvaluationResult

Validate checks the provided data against the compiled JSON schema. It returns an evaluation result that contains validation status and any errors found during validation.

Directories

Path Synopsis
examples
basic command
basic/plugin command
health command
health/plugin command
scan command

Jump to

Keyboard shortcuts

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