feat(plugins): New Plugin System with multi-language PDK support (#4833)

* chore(plugins): remove the old plugins system implementation

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implement new plugin system with using Extism

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add capability detection for plugins based on exported functions

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add auto-reload functionality for plugins with file watcher support

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add auto-reload functionality for plugins with file watcher support

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): standardize variable names and remove superfluous wrapper functions

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): improve error handling and logging in plugin manager

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): implement plugin function call helper and refactor MetadataAgent methods

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): race condition in plugin manager

* tests(plugins): change BeforeEach to BeforeAll in MetadataAgent tests

Signed-off-by: Deluan <deluan@navidrome.org>

* tests(plugins): optimize tests

Signed-off-by: Deluan <deluan@navidrome.org>

* tests(plugins): more optimizations

Signed-off-by: Deluan <deluan@navidrome.org>

* test(plugins): ignore goroutine leaks from notify library in tests

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add Wikimedia plugin for Navidrome to fetch artist metadata

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): enhance plugin logging and set User-Agent header

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implement scrobbler plugin with authorization and scrobbling capabilities

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): integrate logs

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): clean up manifest struct and improve plugin loading logic

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add metadata agent and scrobbler schemas for bootstrapping plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(hostgen): add hostgen tool for generating Extism host function wrappers

- Implemented hostgen tool to generate wrappers from annotated Go interfaces.
- Added command-line flags for input/output directories and package name.
- Introduced parsing and code generation logic for host services.
- Created test data for various service interfaces and expected generated code.
- Added documentation for host services and annotations for code generation.
- Implemented SubsonicAPI service with corresponding generated code.

* feat(subsonicapi): update Call method to return JSON string response

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implement SubsonicAPI host function integration with permissions

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(generator): error-only methods in response handling

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): generate client wrappers for host functions

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(generator): remove error handling for response.Error in client templates

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(scheduler): add Scheduler service interface with host function wrappers for scheduling tasks

* feat(plugins): add WASI build constraints to client wrapper templates, to avoid lint errors

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(scheduler): implement Scheduler service with one-time and recurring scheduling capabilities

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(manifest): remove unused ConfigPermission from permissions schema

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(scheduler): add scheduler callback schema and implementation for plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(scheduler): streamline scheduling logic and remove unused callback tracking

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(scheduler): add Close method for resource cleanup on plugin unload

Signed-off-by: Deluan <deluan@navidrome.org>

* docs(scheduler): clarify SchedulerCallback requirement for scheduling functions

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: update wasm build rule to include all Go files in the directory

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: rewrite the wikimedia plugin using the XTP CLI

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(scheduler): replace uuid with id.NewRandom for schedule ID generation

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: capabilities registration

Signed-off-by: Deluan <deluan@navidrome.org>

* test: add scheduler service isolation test for plugin instances

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: update plugin manager initialization and encapsulate logic

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add WebSocket service definitions for plugin communication

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement WebSocket service for plugin integration and connection management

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Crypto Ticker example plugin for real-time cryptocurrency price updates via Coinbase WebSocket API

Also add the lifecycle capability

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: use context.Background() in invokeCallback for scheduled tasks

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: rename plugin.create() to plugin.instance()

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: rename pluginInstance to plugin for consistency across the codebase

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: simplify schedule cloning in Close method and enhance plugin cleanup error handling

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement Artwork service for generating artwork URLs in Navidrome plugins - WIP

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: moved public URL builders to avoid import cycles

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Cache service for in-memory TTL-based caching in plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Discord Rich Presence example plugin for Navidrome integration

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: host function wrappers to use structured request and response types

- Updated the host function signatures in `nd_host_artwork.go`, `nd_host_scheduler.go`, `nd_host_subsonicapi.go`, and `nd_host_websocket.go` to accept a single parameter for JSON requests.
- Introduced structured request and response types for various cache operations in `nd_host_cache.go`.
- Modified cache functions to marshal requests to JSON and unmarshal responses, improving error handling and code clarity.
- Removed redundant memory allocation for string parameters in favor of JSON marshaling.
- Enhanced error handling in WebSocket and cache operations to return structured error responses.

* refactor: error handling in various plugins to convert response.Error to Go errors

- Updated error handling in `nd_host_scheduler.go`, `nd_host_websocket.go`, `nd_host_artwork.go`, `nd_host_cache.go`, and `nd_host_subsonicapi.go` to convert string errors from responses into Go errors.
- Removed redundant error checks in test data plugins for cleaner code.
- Ensured consistent error handling across all plugins to improve reliability and maintainability.

* refactor: rename fake plugins to test plugins for clarity in integration tests

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add help target to Makefile for plugin usage instructions

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Cover Art Archive plugin as an example of Python plugin

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update Makefile and README to clarify Go plugin usage

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: include plugin capabilities in loading log message

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add trace logging for plugin availability and error handling in agents

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Now Playing Logger plugin to showcase calling host functions from Python plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: generate Python client wrappers for various host services

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add generated host function wrappers for Scheduler and SubsonicAPI services

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update Python plugin documentation and usage instructions for host function wrappers

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add Webhook Scrobbler plugin in Rust to send HTTP notifications on scrobble events

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enable parallel loading of plugins during startup

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update README to include WebSocket callback schema in plugin documentation

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: extend plugin watcher with improved logging and debounce duration adjustment

Signed-off-by: Deluan <deluan@navidrome.org>

* add trace message for plugin recompiles

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement plugin cache purging functionality

Signed-off-by: Deluan <deluan@navidrome.org>

* test: move purgeCacheBySize unit tests

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): add plugin repository and database support

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): add plugin management routes and middleware

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): implement plugin synchronization with database for add, update, and remove actions

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): add PluginList and PluginShow components with plugin management functionality

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): optimize plugin change detection

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins UI): improve PluginList structure

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): enhance PluginShow with author, website, and permissions display

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): refactor to use MUI and RA components

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins UI): add error handling for plugin enable/disable actions

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): inject PluginManager into native API

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update GetManager to accept DataStore parameter

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add subsonicRouter to Manager and refactor host service registration

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): enhance debug logging for plugin actions and recompile logic

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): break manager.go into smaller, focused files

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): streamline error handling and improve plugin retrieval logic

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update newWebSocketService to use WebSocketPermission for allowed hosts

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): introduce ToggleEnabledSwitch for managing plugin enable/disable state

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update READMEs

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(library): add Library service for metadata access and filesystem integration

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add Library Inspector plugin for periodic library inspection and file size logging

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update README to reflect JSON configuration format for plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(build): update target to wasm32-wasip1 for improved WASI support

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implement configuration management UI with key-value pairs support

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(ui): adjust grid layout in InfoRow component for improved responsiveness

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): rename ErrorIndicator to EnabledOrErrorField and enhance error handling logic

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(i18n): add Portuguese translations for plugin management and notifications

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add support for .ndp plugin packages and update build process

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update README for .ndp plugin packaging and installation instructions

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implement KVStore service for persistent key-value storage

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: enhance README with Extism plugin development resources and recommendations

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): integrate event broker into plugin manager

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): update config handling in PluginShow to track last record state

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add Rust host function library and example implementation of Discord Rich Presence plugin in Rust

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): generate Rust lib.rs file to expose host function wrappers

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update JSON field names to camelCase for consistency

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: reduce cyclomatic complexity by refactoring main function

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): enhance Rust code generation with typed struct support and improved type handling

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add Go client library with host function wrappers and documentation

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): generate Go client stubs for non-WASM platforms

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): update client template file names for consistency

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): add initial implementation of the Navidrome Plugin Development Kit code generator - Pahse 1

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 2

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 2 (2)

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 3

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 4

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implementation of the Navidrome Plugin Development Kit with generated client wrappers and service interfaces - Phase 5

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): consistent naming/types across PDK

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): streamline plugin function signatures and error handling

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update scrobbler interface to return errors directly instead of response structs

Signed-off-by: Deluan <deluan@navidrome.org>

* test: make all test plugins use the PDK

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): reorganize and sort type definitions for consistency

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update error handling for methods to return errors directly

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update function signatures to return values directly instead of response structs

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update request/response types to use private naming conventions

Signed-off-by: Deluan <deluan@navidrome.org>

* build: mark .wasm files as intermediate for cleanup after building .ndp

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): consolidate PDK module path and update Go version to 1.25

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement Rust PDK

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): reorganize Rust output structure to follow standard conventions

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update Discord Rich Presence and Library Inspector plugins to use nd-pdk for service calls and implement lifecycle management

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update macro names for websocket and metadata registration to improve clarity and consistency

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): rename scheduler callback methods for consistency and clarity

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update export wrappers to use `//go:wasmexport` for WebAssembly compatibility

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update plugin registration docs

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): generate host wrappers

Signed-off-by: Deluan <deluan@navidrome.org>

* test(plugins): conditionally run goleak checks based on CI environment

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update README to reflect changes in plugin import paths

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(plugins): update plugin instance creation to accept context for cancellation support

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): update return types in metadata interfaces to use pointers

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): enhance type handling for Rust and XTP output in capability generation

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): update IsAuthorized method to return boolean instead of response object

Signed-off-by: Deluan <deluan@navidrome.org>

* test(plugins): add unit tests for rustOutputType and isPrimitiveRustType functions

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(plugins): implement XTP JSONSchema validation for generated schemas

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(plugins): update response types in testMetadataAgent methods to use pointers

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update Go and Rust plugin developer sections for clarity

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: correct example link for library inspector in README

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: clarify artwork URL generation capabilities in service descriptions

Signed-off-by: Deluan <deluan@navidrome.org>

* docs: update README to include Rust PDK crate information for plugin developers

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: handle URL parsing errors and use atomic upsert in plugin repository

Added proper error handling for url.Parse calls in PublicURL and AbsoluteURL
functions. When parsing fails, PublicURL now falls back to AbsoluteURL, and
AbsoluteURL logs the error and returns an empty string, preventing malformed
URLs from being generated.

Replaced the non-atomic UPDATE-then-INSERT pattern in plugin repository Put
method with a single atomic INSERT ... ON CONFLICT statement. This eliminates
potential race conditions and improves consistency with the upsert pattern
already used in host_kvstore.go.

* feat: implement mock service instances for non-WASM builds using testify/mock

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: Discord RPC struct to encapsulate WebSocket logic

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add support for experimental WebAssembly threads

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add PDK abstraction layer with mock support for non-WASM builds

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add unit tests for Discord plugin and RPC functionality

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: update return types in minimalPlugin and wikimediaPlugin methods to use pointers

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: context cancellation and implement WebSocket callback timeout for improved error handling

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: conditionally include error handling in generated client code templates

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement ConfigService for plugin configuration management

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance plugin manager to support metrics recording

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: make MockPDK private

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: update interface types to use 'any' in plugin repository methods

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: rename List method to Keys for clarity in configuration management

Signed-off-by: Deluan <deluan@navidrome.org>

* test: add ndpgen plugin tests in the pipeline and update Makefile

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add users permission management to plugin system

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: streamline users integration tests and enhance plugin user management

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor: remove UserID from scrobbler request structure

Signed-off-by: Deluan <deluan@navidrome.org>

* test: add integration tests for UsersService enable gate behavior

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement user permissions for SubsonicAPI and scrobbler plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: show proper error in the UI when enabling a plugin fails

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add library permission management to plugin system

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add user permission for processing scrobbles in Discord Rich Presence plugin

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: implement dynamic loading for buffered scrobbler plugins

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: add GetAdmins method to retrieve admin users from the plugin

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update Portuguese translations for user and library permissions

Signed-off-by: Deluan <deluan@navidrome.org>

* reorder migrations

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: remove unnecessary bulkActionButtons prop from PluginList component

* feat: add manual plugin rescan functionality and corresponding UI action

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement user/library and plugin management integration with cleanup on deletion

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: replace core mock services with test-specific implementations to avoid import cycles

* feat: add ID fields to Artist and Song structs and enhance track loading logic by prioritizing ID matches

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update plugin permissions from allowedHosts to requiredHosts for better clarity and consistency

* feat: refactor plugin host permissions to use RequiredHosts directly for improved clarity

* fix: don't record metrics for plugin calls that aren't implemented at all

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: enhance connection management with improved error handling and cleanup logic

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: introduce ArtistRef struct for better artist representation and update track metadata handling

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: update user configuration handling to use user key prefix for improved clarity

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: enhance ConfigCard input fields with multiline support and vertical resizing

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: rust plugin compilation error

Signed-off-by: Deluan <deluan@navidrome.org>

* feat: implement IsOptionPattern method for better return type handling in Rust PDK generation

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan Quintão
2026-01-14 19:22:48 -05:00
committed by GitHub
parent fd4a04339e
commit 03a45753e9
518 changed files with 49456 additions and 34933 deletions
+53
View File
@@ -0,0 +1,53 @@
package host
import "context"
// ArtworkService provides artwork public URL generation capabilities for plugins.
//
// This service allows plugins to generate public URLs for artwork images of
// various entity types (artists, albums, tracks, playlists). The generated URLs
// include authentication tokens and can be used to display artwork in external
// services or custom UIs.
//
//nd:hostservice name=Artwork permission=artwork
type ArtworkService interface {
// GetArtistUrl generates a public URL for an artist's artwork.
//
// Parameters:
// - id: The artist's unique identifier
// - size: Desired image size in pixels (0 for original size)
//
// Returns the public URL for the artwork, or an error if generation fails.
//nd:hostfunc
GetArtistUrl(ctx context.Context, id string, size int32) (url string, err error)
// GetAlbumUrl generates a public URL for an album's artwork.
//
// Parameters:
// - id: The album's unique identifier
// - size: Desired image size in pixels (0 for original size)
//
// Returns the public URL for the artwork, or an error if generation fails.
//nd:hostfunc
GetAlbumUrl(ctx context.Context, id string, size int32) (url string, err error)
// GetTrackUrl generates a public URL for a track's artwork.
//
// Parameters:
// - id: The track's (media file) unique identifier
// - size: Desired image size in pixels (0 for original size)
//
// Returns the public URL for the artwork, or an error if generation fails.
//nd:hostfunc
GetTrackUrl(ctx context.Context, id string, size int32) (url string, err error)
// GetPlaylistUrl generates a public URL for a playlist's artwork.
//
// Parameters:
// - id: The playlist's unique identifier
// - size: Desired image size in pixels (0 for original size)
//
// Returns the public URL for the artwork, or an error if generation fails.
//nd:hostfunc
GetPlaylistUrl(ctx context.Context, id string, size int32) (url string, err error)
}
-73
View File
@@ -1,73 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/artwork/artwork.proto
package artwork
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetArtworkUrlRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Size int32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` // Optional, 0 means original size
}
func (x *GetArtworkUrlRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetArtworkUrlRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *GetArtworkUrlRequest) GetSize() int32 {
if x != nil {
return x.Size
}
return 0
}
type GetArtworkUrlResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
}
func (x *GetArtworkUrlResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetArtworkUrlResponse) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
// go:plugin type=host version=1
type ArtworkService interface {
GetArtistUrl(context.Context, *GetArtworkUrlRequest) (*GetArtworkUrlResponse, error)
GetAlbumUrl(context.Context, *GetArtworkUrlRequest) (*GetArtworkUrlResponse, error)
GetTrackUrl(context.Context, *GetArtworkUrlRequest) (*GetArtworkUrlResponse, error)
}
-21
View File
@@ -1,21 +0,0 @@
syntax = "proto3";
package artwork;
option go_package = "github.com/navidrome/navidrome/plugins/host/artwork;artwork";
// go:plugin type=host version=1
service ArtworkService {
rpc GetArtistUrl(GetArtworkUrlRequest) returns (GetArtworkUrlResponse);
rpc GetAlbumUrl(GetArtworkUrlRequest) returns (GetArtworkUrlResponse);
rpc GetTrackUrl(GetArtworkUrlRequest) returns (GetArtworkUrlResponse);
}
message GetArtworkUrlRequest {
string id = 1;
int32 size = 2; // Optional, 0 means original size
}
message GetArtworkUrlResponse {
string url = 1;
}
-130
View File
@@ -1,130 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/artwork/artwork.proto
package artwork
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _artworkService struct {
ArtworkService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions ArtworkService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _artworkService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetArtistUrl), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_artist_url")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetAlbumUrl), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_album_url")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetTrackUrl), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_track_url")
_, err := envBuilder.Instantiate(ctx)
return err
}
func (h _artworkService) _GetArtistUrl(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetArtworkUrlRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetArtistUrl(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _artworkService) _GetAlbumUrl(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetArtworkUrlRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetAlbumUrl(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _artworkService) _GetTrackUrl(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetArtworkUrlRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetTrackUrl(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
-90
View File
@@ -1,90 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/artwork/artwork.proto
package artwork
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type artworkService struct{}
func NewArtworkService() ArtworkService {
return artworkService{}
}
//go:wasmimport env get_artist_url
func _get_artist_url(ptr uint32, size uint32) uint64
func (h artworkService) GetArtistUrl(ctx context.Context, request *GetArtworkUrlRequest) (*GetArtworkUrlResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_artist_url(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetArtworkUrlResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env get_album_url
func _get_album_url(ptr uint32, size uint32) uint64
func (h artworkService) GetAlbumUrl(ctx context.Context, request *GetArtworkUrlRequest) (*GetArtworkUrlResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_album_url(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetArtworkUrlResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env get_track_url
func _get_track_url(ptr uint32, size uint32) uint64
func (h artworkService) GetTrackUrl(ctx context.Context, request *GetArtworkUrlRequest) (*GetArtworkUrlResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_track_url(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetArtworkUrlResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
@@ -1,7 +0,0 @@
//go:build !wasip1
package artwork
func NewArtworkService() ArtworkService {
panic("not implemented")
}
-425
View File
@@ -1,425 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/artwork/artwork.proto
package artwork
import (
fmt "fmt"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
bits "math/bits"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *GetArtworkUrlRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetArtworkUrlRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *GetArtworkUrlRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.Size != 0 {
i = encodeVarint(dAtA, i, uint64(m.Size))
i--
dAtA[i] = 0x10
}
if len(m.Id) > 0 {
i -= len(m.Id)
copy(dAtA[i:], m.Id)
i = encodeVarint(dAtA, i, uint64(len(m.Id)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *GetArtworkUrlResponse) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetArtworkUrlResponse) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *GetArtworkUrlResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Url) > 0 {
i -= len(m.Url)
copy(dAtA[i:], m.Url)
i = encodeVarint(dAtA, i, uint64(len(m.Url)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarint(dAtA []byte, offset int, v uint64) int {
offset -= sov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *GetArtworkUrlRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Id)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if m.Size != 0 {
n += 1 + sov(uint64(m.Size))
}
n += len(m.unknownFields)
return n
}
func (m *GetArtworkUrlResponse) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Url)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
n += len(m.unknownFields)
return n
}
func sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func soz(x uint64) (n int) {
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *GetArtworkUrlRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetArtworkUrlRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetArtworkUrlRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Id = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Size", wireType)
}
m.Size = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Size |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *GetArtworkUrlResponse) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetArtworkUrlResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetArtworkUrlResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Url = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skip(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLength
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLength
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
)
+230
View File
@@ -0,0 +1,230 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl.
type ArtworkGetArtistUrlRequest struct {
Id string `json:"id"`
Size int32 `json:"size"`
}
// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
type ArtworkGetArtistUrlResponse struct {
Url string `json:"url,omitempty"`
Error string `json:"error,omitempty"`
}
// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl.
type ArtworkGetAlbumUrlRequest struct {
Id string `json:"id"`
Size int32 `json:"size"`
}
// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
type ArtworkGetAlbumUrlResponse struct {
Url string `json:"url,omitempty"`
Error string `json:"error,omitempty"`
}
// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl.
type ArtworkGetTrackUrlRequest struct {
Id string `json:"id"`
Size int32 `json:"size"`
}
// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
type ArtworkGetTrackUrlResponse struct {
Url string `json:"url,omitempty"`
Error string `json:"error,omitempty"`
}
// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl.
type ArtworkGetPlaylistUrlRequest struct {
Id string `json:"id"`
Size int32 `json:"size"`
}
// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
type ArtworkGetPlaylistUrlResponse struct {
Url string `json:"url,omitempty"`
Error string `json:"error,omitempty"`
}
// RegisterArtworkHostFunctions registers Artwork service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterArtworkHostFunctions(service ArtworkService) []extism.HostFunction {
return []extism.HostFunction{
newArtworkGetArtistUrlHostFunction(service),
newArtworkGetAlbumUrlHostFunction(service),
newArtworkGetTrackUrlHostFunction(service),
newArtworkGetPlaylistUrlHostFunction(service),
}
}
func newArtworkGetArtistUrlHostFunction(service ArtworkService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"artwork_getartisturl",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
artworkWriteError(p, stack, err)
return
}
var req ArtworkGetArtistUrlRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
artworkWriteError(p, stack, err)
return
}
// Call the service method
url, svcErr := service.GetArtistUrl(ctx, req.Id, req.Size)
if svcErr != nil {
artworkWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := ArtworkGetArtistUrlResponse{
Url: url,
}
artworkWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newArtworkGetAlbumUrlHostFunction(service ArtworkService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"artwork_getalbumurl",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
artworkWriteError(p, stack, err)
return
}
var req ArtworkGetAlbumUrlRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
artworkWriteError(p, stack, err)
return
}
// Call the service method
url, svcErr := service.GetAlbumUrl(ctx, req.Id, req.Size)
if svcErr != nil {
artworkWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := ArtworkGetAlbumUrlResponse{
Url: url,
}
artworkWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newArtworkGetTrackUrlHostFunction(service ArtworkService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"artwork_gettrackurl",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
artworkWriteError(p, stack, err)
return
}
var req ArtworkGetTrackUrlRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
artworkWriteError(p, stack, err)
return
}
// Call the service method
url, svcErr := service.GetTrackUrl(ctx, req.Id, req.Size)
if svcErr != nil {
artworkWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := ArtworkGetTrackUrlResponse{
Url: url,
}
artworkWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newArtworkGetPlaylistUrlHostFunction(service ArtworkService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"artwork_getplaylisturl",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
artworkWriteError(p, stack, err)
return
}
var req ArtworkGetPlaylistUrlRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
artworkWriteError(p, stack, err)
return
}
// Call the service method
url, svcErr := service.GetPlaylistUrl(ctx, req.Id, req.Size)
if svcErr != nil {
artworkWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := ArtworkGetPlaylistUrlResponse{
Url: url,
}
artworkWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// artworkWriteResponse writes a JSON response to plugin memory.
func artworkWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
artworkWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// artworkWriteError writes an error response to plugin memory.
func artworkWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+117
View File
@@ -0,0 +1,117 @@
package host
import "context"
// CacheService provides in-memory TTL-based caching capabilities for plugins.
//
// This service allows plugins to store and retrieve typed values (strings, integers,
// floats, and byte slices) with configurable time-to-live expiration. Each plugin's
// cache keys are automatically namespaced to prevent collisions between plugins.
//
// The cache is in-memory only and will be lost on server restart. Plugins should
// handle cache misses gracefully.
//
//nd:hostservice name=Cache permission=cache
type CacheService interface {
// SetString stores a string value in the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
// - value: The string value to store
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
//
// Returns an error if the operation fails.
//nd:hostfunc
SetString(ctx context.Context, key string, value string, ttlSeconds int64) error
// GetString retrieves a string value from the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
//
// Returns the value and whether the key exists. If the key doesn't exist
// or the stored value is not a string, exists will be false.
//nd:hostfunc
GetString(ctx context.Context, key string) (value string, exists bool, err error)
// SetInt stores an integer value in the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
// - value: The integer value to store
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
//
// Returns an error if the operation fails.
//nd:hostfunc
SetInt(ctx context.Context, key string, value int64, ttlSeconds int64) error
// GetInt retrieves an integer value from the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
//
// Returns the value and whether the key exists. If the key doesn't exist
// or the stored value is not an integer, exists will be false.
//nd:hostfunc
GetInt(ctx context.Context, key string) (value int64, exists bool, err error)
// SetFloat stores a float value in the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
// - value: The float value to store
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
//
// Returns an error if the operation fails.
//nd:hostfunc
SetFloat(ctx context.Context, key string, value float64, ttlSeconds int64) error
// GetFloat retrieves a float value from the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
//
// Returns the value and whether the key exists. If the key doesn't exist
// or the stored value is not a float, exists will be false.
//nd:hostfunc
GetFloat(ctx context.Context, key string) (value float64, exists bool, err error)
// SetBytes stores a byte slice in the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
// - value: The byte slice to store
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
//
// Returns an error if the operation fails.
//nd:hostfunc
SetBytes(ctx context.Context, key string, value []byte, ttlSeconds int64) error
// GetBytes retrieves a byte slice from the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
//
// Returns the value and whether the key exists. If the key doesn't exist
// or the stored value is not a byte slice, exists will be false.
//nd:hostfunc
GetBytes(ctx context.Context, key string) (value []byte, exists bool, err error)
// Has checks if a key exists in the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
//
// Returns true if the key exists and has not expired.
//nd:hostfunc
Has(ctx context.Context, key string) (exists bool, err error)
// Remove deletes a value from the cache.
//
// Parameters:
// - key: The cache key (will be namespaced with plugin ID)
//
// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
//nd:hostfunc
Remove(ctx context.Context, key string) error
}
-420
View File
@@ -1,420 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/cache/cache.proto
package cache
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Request to store a string value
type SetStringRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // String value to store
TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` // TTL in seconds, 0 means use default
}
func (x *SetStringRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SetStringRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *SetStringRequest) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
func (x *SetStringRequest) GetTtlSeconds() int64 {
if x != nil {
return x.TtlSeconds
}
return 0
}
// Request to store an integer value
type SetIntRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` // Integer value to store
TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` // TTL in seconds, 0 means use default
}
func (x *SetIntRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SetIntRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *SetIntRequest) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
func (x *SetIntRequest) GetTtlSeconds() int64 {
if x != nil {
return x.TtlSeconds
}
return 0
}
// Request to store a float value
type SetFloatRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
Value float64 `protobuf:"fixed64,2,opt,name=value,proto3" json:"value,omitempty"` // Float value to store
TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` // TTL in seconds, 0 means use default
}
func (x *SetFloatRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SetFloatRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *SetFloatRequest) GetValue() float64 {
if x != nil {
return x.Value
}
return 0
}
func (x *SetFloatRequest) GetTtlSeconds() int64 {
if x != nil {
return x.TtlSeconds
}
return 0
}
// Request to store a byte slice value
type SetBytesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // Byte slice value to store
TtlSeconds int64 `protobuf:"varint,3,opt,name=ttl_seconds,json=ttlSeconds,proto3" json:"ttl_seconds,omitempty"` // TTL in seconds, 0 means use default
}
func (x *SetBytesRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SetBytesRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *SetBytesRequest) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
func (x *SetBytesRequest) GetTtlSeconds() int64 {
if x != nil {
return x.TtlSeconds
}
return 0
}
// Response after setting a value
type SetResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // Whether the operation was successful
}
func (x *SetResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SetResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
// Request to get a value
type GetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
}
func (x *GetRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
// Response containing a string value
type GetStringResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` // Whether the key exists
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // The string value (if exists is true)
}
func (x *GetStringResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetStringResponse) GetExists() bool {
if x != nil {
return x.Exists
}
return false
}
func (x *GetStringResponse) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
// Response containing an integer value
type GetIntResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` // Whether the key exists
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` // The integer value (if exists is true)
}
func (x *GetIntResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetIntResponse) GetExists() bool {
if x != nil {
return x.Exists
}
return false
}
func (x *GetIntResponse) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
// Response containing a float value
type GetFloatResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` // Whether the key exists
Value float64 `protobuf:"fixed64,2,opt,name=value,proto3" json:"value,omitempty"` // The float value (if exists is true)
}
func (x *GetFloatResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetFloatResponse) GetExists() bool {
if x != nil {
return x.Exists
}
return false
}
func (x *GetFloatResponse) GetValue() float64 {
if x != nil {
return x.Value
}
return 0
}
// Response containing a byte slice value
type GetBytesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` // Whether the key exists
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // The byte slice value (if exists is true)
}
func (x *GetBytesResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetBytesResponse) GetExists() bool {
if x != nil {
return x.Exists
}
return false
}
func (x *GetBytesResponse) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
// Request to remove a value
type RemoveRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
}
func (x *RemoveRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *RemoveRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
// Response after removing a value
type RemoveResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // Whether the operation was successful
}
func (x *RemoveResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *RemoveResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
// Request to check if a key exists
type HasRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Cache key
}
func (x *HasRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *HasRequest) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
// Response indicating if a key exists
type HasResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` // Whether the key exists
}
func (x *HasResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *HasResponse) GetExists() bool {
if x != nil {
return x.Exists
}
return false
}
// go:plugin type=host version=1
type CacheService interface {
// Set a string value in the cache
SetString(context.Context, *SetStringRequest) (*SetResponse, error)
// Get a string value from the cache
GetString(context.Context, *GetRequest) (*GetStringResponse, error)
// Set an integer value in the cache
SetInt(context.Context, *SetIntRequest) (*SetResponse, error)
// Get an integer value from the cache
GetInt(context.Context, *GetRequest) (*GetIntResponse, error)
// Set a float value in the cache
SetFloat(context.Context, *SetFloatRequest) (*SetResponse, error)
// Get a float value from the cache
GetFloat(context.Context, *GetRequest) (*GetFloatResponse, error)
// Set a byte slice value in the cache
SetBytes(context.Context, *SetBytesRequest) (*SetResponse, error)
// Get a byte slice value from the cache
GetBytes(context.Context, *GetRequest) (*GetBytesResponse, error)
// Remove a value from the cache
Remove(context.Context, *RemoveRequest) (*RemoveResponse, error)
// Check if a key exists in the cache
Has(context.Context, *HasRequest) (*HasResponse, error)
}
-120
View File
@@ -1,120 +0,0 @@
syntax = "proto3";
package cache;
option go_package = "github.com/navidrome/navidrome/plugins/host/cache;cache";
// go:plugin type=host version=1
service CacheService {
// Set a string value in the cache
rpc SetString(SetStringRequest) returns (SetResponse);
// Get a string value from the cache
rpc GetString(GetRequest) returns (GetStringResponse);
// Set an integer value in the cache
rpc SetInt(SetIntRequest) returns (SetResponse);
// Get an integer value from the cache
rpc GetInt(GetRequest) returns (GetIntResponse);
// Set a float value in the cache
rpc SetFloat(SetFloatRequest) returns (SetResponse);
// Get a float value from the cache
rpc GetFloat(GetRequest) returns (GetFloatResponse);
// Set a byte slice value in the cache
rpc SetBytes(SetBytesRequest) returns (SetResponse);
// Get a byte slice value from the cache
rpc GetBytes(GetRequest) returns (GetBytesResponse);
// Remove a value from the cache
rpc Remove(RemoveRequest) returns (RemoveResponse);
// Check if a key exists in the cache
rpc Has(HasRequest) returns (HasResponse);
}
// Request to store a string value
message SetStringRequest {
string key = 1; // Cache key
string value = 2; // String value to store
int64 ttl_seconds = 3; // TTL in seconds, 0 means use default
}
// Request to store an integer value
message SetIntRequest {
string key = 1; // Cache key
int64 value = 2; // Integer value to store
int64 ttl_seconds = 3; // TTL in seconds, 0 means use default
}
// Request to store a float value
message SetFloatRequest {
string key = 1; // Cache key
double value = 2; // Float value to store
int64 ttl_seconds = 3; // TTL in seconds, 0 means use default
}
// Request to store a byte slice value
message SetBytesRequest {
string key = 1; // Cache key
bytes value = 2; // Byte slice value to store
int64 ttl_seconds = 3; // TTL in seconds, 0 means use default
}
// Response after setting a value
message SetResponse {
bool success = 1; // Whether the operation was successful
}
// Request to get a value
message GetRequest {
string key = 1; // Cache key
}
// Response containing a string value
message GetStringResponse {
bool exists = 1; // Whether the key exists
string value = 2; // The string value (if exists is true)
}
// Response containing an integer value
message GetIntResponse {
bool exists = 1; // Whether the key exists
int64 value = 2; // The integer value (if exists is true)
}
// Response containing a float value
message GetFloatResponse {
bool exists = 1; // Whether the key exists
double value = 2; // The float value (if exists is true)
}
// Response containing a byte slice value
message GetBytesResponse {
bool exists = 1; // Whether the key exists
bytes value = 2; // The byte slice value (if exists is true)
}
// Request to remove a value
message RemoveRequest {
string key = 1; // Cache key
}
// Response after removing a value
message RemoveResponse {
bool success = 1; // Whether the operation was successful
}
// Request to check if a key exists
message HasRequest {
string key = 1; // Cache key
}
// Response indicating if a key exists
message HasResponse {
bool exists = 1; // Whether the key exists
}
-374
View File
@@ -1,374 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/cache/cache.proto
package cache
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _cacheService struct {
CacheService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions CacheService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _cacheService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._SetString), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("set_string")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetString), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_string")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._SetInt), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("set_int")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetInt), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_int")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._SetFloat), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("set_float")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetFloat), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_float")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._SetBytes), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("set_bytes")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetBytes), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_bytes")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Remove), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("remove")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Has), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("has")
_, err := envBuilder.Instantiate(ctx)
return err
}
// Set a string value in the cache
func (h _cacheService) _SetString(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(SetStringRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.SetString(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Get a string value from the cache
func (h _cacheService) _GetString(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetString(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Set an integer value in the cache
func (h _cacheService) _SetInt(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(SetIntRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.SetInt(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Get an integer value from the cache
func (h _cacheService) _GetInt(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetInt(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Set a float value in the cache
func (h _cacheService) _SetFloat(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(SetFloatRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.SetFloat(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Get a float value from the cache
func (h _cacheService) _GetFloat(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetFloat(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Set a byte slice value in the cache
func (h _cacheService) _SetBytes(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(SetBytesRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.SetBytes(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Get a byte slice value from the cache
func (h _cacheService) _GetBytes(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetBytes(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Remove a value from the cache
func (h _cacheService) _Remove(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(RemoveRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Remove(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Check if a key exists in the cache
func (h _cacheService) _Has(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HasRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Has(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
-251
View File
@@ -1,251 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/cache/cache.proto
package cache
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type cacheService struct{}
func NewCacheService() CacheService {
return cacheService{}
}
//go:wasmimport env set_string
func _set_string(ptr uint32, size uint32) uint64
func (h cacheService) SetString(ctx context.Context, request *SetStringRequest) (*SetResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _set_string(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(SetResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env get_string
func _get_string(ptr uint32, size uint32) uint64
func (h cacheService) GetString(ctx context.Context, request *GetRequest) (*GetStringResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_string(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetStringResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env set_int
func _set_int(ptr uint32, size uint32) uint64
func (h cacheService) SetInt(ctx context.Context, request *SetIntRequest) (*SetResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _set_int(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(SetResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env get_int
func _get_int(ptr uint32, size uint32) uint64
func (h cacheService) GetInt(ctx context.Context, request *GetRequest) (*GetIntResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_int(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetIntResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env set_float
func _set_float(ptr uint32, size uint32) uint64
func (h cacheService) SetFloat(ctx context.Context, request *SetFloatRequest) (*SetResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _set_float(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(SetResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env get_float
func _get_float(ptr uint32, size uint32) uint64
func (h cacheService) GetFloat(ctx context.Context, request *GetRequest) (*GetFloatResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_float(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetFloatResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env set_bytes
func _set_bytes(ptr uint32, size uint32) uint64
func (h cacheService) SetBytes(ctx context.Context, request *SetBytesRequest) (*SetResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _set_bytes(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(SetResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env get_bytes
func _get_bytes(ptr uint32, size uint32) uint64
func (h cacheService) GetBytes(ctx context.Context, request *GetRequest) (*GetBytesResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_bytes(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetBytesResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env remove
func _remove(ptr uint32, size uint32) uint64
func (h cacheService) Remove(ctx context.Context, request *RemoveRequest) (*RemoveResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _remove(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(RemoveResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env has
func _has(ptr uint32, size uint32) uint64
func (h cacheService) Has(ctx context.Context, request *HasRequest) (*HasResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _has(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HasResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
-7
View File
@@ -1,7 +0,0 @@
//go:build !wasip1
package cache
func NewCacheService() CacheService {
panic("not implemented")
}
File diff suppressed because it is too large Load Diff
+498
View File
@@ -0,0 +1,498 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// CacheSetStringRequest is the request type for Cache.SetString.
type CacheSetStringRequest struct {
Key string `json:"key"`
Value string `json:"value"`
TtlSeconds int64 `json:"ttlSeconds"`
}
// CacheSetStringResponse is the response type for Cache.SetString.
type CacheSetStringResponse struct {
Error string `json:"error,omitempty"`
}
// CacheGetStringRequest is the request type for Cache.GetString.
type CacheGetStringRequest struct {
Key string `json:"key"`
}
// CacheGetStringResponse is the response type for Cache.GetString.
type CacheGetStringResponse struct {
Value string `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// CacheSetIntRequest is the request type for Cache.SetInt.
type CacheSetIntRequest struct {
Key string `json:"key"`
Value int64 `json:"value"`
TtlSeconds int64 `json:"ttlSeconds"`
}
// CacheSetIntResponse is the response type for Cache.SetInt.
type CacheSetIntResponse struct {
Error string `json:"error,omitempty"`
}
// CacheGetIntRequest is the request type for Cache.GetInt.
type CacheGetIntRequest struct {
Key string `json:"key"`
}
// CacheGetIntResponse is the response type for Cache.GetInt.
type CacheGetIntResponse struct {
Value int64 `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// CacheSetFloatRequest is the request type for Cache.SetFloat.
type CacheSetFloatRequest struct {
Key string `json:"key"`
Value float64 `json:"value"`
TtlSeconds int64 `json:"ttlSeconds"`
}
// CacheSetFloatResponse is the response type for Cache.SetFloat.
type CacheSetFloatResponse struct {
Error string `json:"error,omitempty"`
}
// CacheGetFloatRequest is the request type for Cache.GetFloat.
type CacheGetFloatRequest struct {
Key string `json:"key"`
}
// CacheGetFloatResponse is the response type for Cache.GetFloat.
type CacheGetFloatResponse struct {
Value float64 `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// CacheSetBytesRequest is the request type for Cache.SetBytes.
type CacheSetBytesRequest struct {
Key string `json:"key"`
Value []byte `json:"value"`
TtlSeconds int64 `json:"ttlSeconds"`
}
// CacheSetBytesResponse is the response type for Cache.SetBytes.
type CacheSetBytesResponse struct {
Error string `json:"error,omitempty"`
}
// CacheGetBytesRequest is the request type for Cache.GetBytes.
type CacheGetBytesRequest struct {
Key string `json:"key"`
}
// CacheGetBytesResponse is the response type for Cache.GetBytes.
type CacheGetBytesResponse struct {
Value []byte `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// CacheHasRequest is the request type for Cache.Has.
type CacheHasRequest struct {
Key string `json:"key"`
}
// CacheHasResponse is the response type for Cache.Has.
type CacheHasResponse struct {
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// CacheRemoveRequest is the request type for Cache.Remove.
type CacheRemoveRequest struct {
Key string `json:"key"`
}
// CacheRemoveResponse is the response type for Cache.Remove.
type CacheRemoveResponse struct {
Error string `json:"error,omitempty"`
}
// RegisterCacheHostFunctions registers Cache service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterCacheHostFunctions(service CacheService) []extism.HostFunction {
return []extism.HostFunction{
newCacheSetStringHostFunction(service),
newCacheGetStringHostFunction(service),
newCacheSetIntHostFunction(service),
newCacheGetIntHostFunction(service),
newCacheSetFloatHostFunction(service),
newCacheGetFloatHostFunction(service),
newCacheSetBytesHostFunction(service),
newCacheGetBytesHostFunction(service),
newCacheHasHostFunction(service),
newCacheRemoveHostFunction(service),
}
}
func newCacheSetStringHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_setstring",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheSetStringRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.SetString(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheSetStringResponse{}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheGetStringHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_getstring",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheGetStringRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
value, exists, svcErr := service.GetString(ctx, req.Key)
if svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheGetStringResponse{
Value: value,
Exists: exists,
}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheSetIntHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_setint",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheSetIntRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.SetInt(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheSetIntResponse{}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheGetIntHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_getint",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheGetIntRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
value, exists, svcErr := service.GetInt(ctx, req.Key)
if svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheGetIntResponse{
Value: value,
Exists: exists,
}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheSetFloatHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_setfloat",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheSetFloatRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.SetFloat(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheSetFloatResponse{}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheGetFloatHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_getfloat",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheGetFloatRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
value, exists, svcErr := service.GetFloat(ctx, req.Key)
if svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheGetFloatResponse{
Value: value,
Exists: exists,
}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheSetBytesHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_setbytes",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheSetBytesRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.SetBytes(ctx, req.Key, req.Value, req.TtlSeconds); svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheSetBytesResponse{}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheGetBytesHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_getbytes",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheGetBytesRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
value, exists, svcErr := service.GetBytes(ctx, req.Key)
if svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheGetBytesResponse{
Value: value,
Exists: exists,
}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheHasHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_has",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheHasRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
exists, svcErr := service.Has(ctx, req.Key)
if svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheHasResponse{
Exists: exists,
}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newCacheRemoveHostFunction(service CacheService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"cache_remove",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
cacheWriteError(p, stack, err)
return
}
var req CacheRemoveRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
cacheWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.Remove(ctx, req.Key); svcErr != nil {
cacheWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := CacheRemoveResponse{}
cacheWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// cacheWriteResponse writes a JSON response to plugin memory.
func cacheWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
cacheWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// cacheWriteError writes an error response to plugin memory.
func cacheWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+44
View File
@@ -0,0 +1,44 @@
package host
import "context"
// ConfigService provides access to plugin configuration values.
//
// This service allows plugins to retrieve configuration values and enumerate
// available configuration keys. Unlike the built-in pdk.GetConfig(key) which
// only retrieves individual values, this service provides methods to list all
// available keys, making it useful for plugins that need to discover dynamic
// configuration (e.g., user-to-token mappings).
//
// This service is always available and does not require a permission in the manifest.
//
//nd:hostservice name=Config
type ConfigService interface {
// Get retrieves a configuration value as a string.
//
// Parameters:
// - key: The configuration key
//
// Returns the value and whether the key exists.
//nd:hostfunc
Get(ctx context.Context, key string) (value string, exists bool)
// GetInt retrieves a configuration value as an integer.
//
// Parameters:
// - key: The configuration key
//
// Returns the value and whether the key exists. If the key exists but the
// value cannot be parsed as an integer, exists will be false.
//nd:hostfunc
GetInt(ctx context.Context, key string) (value int64, exists bool)
// Keys returns configuration keys matching the given prefix.
//
// Parameters:
// - prefix: Key prefix to filter by. If empty, returns all keys.
//
// Returns a sorted slice of matching configuration keys.
//nd:hostfunc
Keys(ctx context.Context, prefix string) (keys []string)
}
-54
View File
@@ -1,54 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/config/config.proto
package config
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetPluginConfigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetPluginConfigRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
type GetPluginConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *GetPluginConfigResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *GetPluginConfigResponse) GetConfig() map[string]string {
if x != nil {
return x.Config
}
return nil
}
// go:plugin type=host version=1
type ConfigService interface {
GetPluginConfig(context.Context, *GetPluginConfigRequest) (*GetPluginConfigResponse, error)
}
-18
View File
@@ -1,18 +0,0 @@
syntax = "proto3";
package config;
option go_package = "github.com/navidrome/navidrome/plugins/host/config;config";
// go:plugin type=host version=1
service ConfigService {
rpc GetPluginConfig(GetPluginConfigRequest) returns (GetPluginConfigResponse);
}
message GetPluginConfigRequest {
// No fields needed; plugin name is inferred from context
}
message GetPluginConfigResponse {
map<string, string> config = 1;
}
-66
View File
@@ -1,66 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/config/config.proto
package config
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _configService struct {
ConfigService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions ConfigService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _configService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._GetPluginConfig), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get_plugin_config")
_, err := envBuilder.Instantiate(ctx)
return err
}
func (h _configService) _GetPluginConfig(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(GetPluginConfigRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.GetPluginConfig(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
-44
View File
@@ -1,44 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/config/config.proto
package config
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type configService struct{}
func NewConfigService() ConfigService {
return configService{}
}
//go:wasmimport env get_plugin_config
func _get_plugin_config(ptr uint32, size uint32) uint64
func (h configService) GetPluginConfig(ctx context.Context, request *GetPluginConfigRequest) (*GetPluginConfigResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get_plugin_config(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(GetPluginConfigResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
-7
View File
@@ -1,7 +0,0 @@
//go:build !wasip1
package config
func NewConfigService() ConfigService {
panic("not implemented")
}
-466
View File
@@ -1,466 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/config/config.proto
package config
import (
fmt "fmt"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
bits "math/bits"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *GetPluginConfigRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetPluginConfigRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *GetPluginConfigRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
return len(dAtA) - i, nil
}
func (m *GetPluginConfigResponse) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetPluginConfigResponse) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *GetPluginConfigResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Config) > 0 {
for k := range m.Config {
v := m.Config[k]
baseI := i
i -= len(v)
copy(dAtA[i:], v)
i = encodeVarint(dAtA, i, uint64(len(v)))
i--
dAtA[i] = 0x12
i -= len(k)
copy(dAtA[i:], k)
i = encodeVarint(dAtA, i, uint64(len(k)))
i--
dAtA[i] = 0xa
i = encodeVarint(dAtA, i, uint64(baseI-i))
i--
dAtA[i] = 0xa
}
}
return len(dAtA) - i, nil
}
func encodeVarint(dAtA []byte, offset int, v uint64) int {
offset -= sov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *GetPluginConfigRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
n += len(m.unknownFields)
return n
}
func (m *GetPluginConfigResponse) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if len(m.Config) > 0 {
for k, v := range m.Config {
_ = k
_ = v
mapEntrySize := 1 + len(k) + sov(uint64(len(k))) + 1 + len(v) + sov(uint64(len(v)))
n += mapEntrySize + 1 + sov(uint64(mapEntrySize))
}
}
n += len(m.unknownFields)
return n
}
func sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func soz(x uint64) (n int) {
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *GetPluginConfigRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetPluginConfigRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetPluginConfigRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *GetPluginConfigResponse) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetPluginConfigResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetPluginConfigResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Config == nil {
m.Config = make(map[string]string)
}
var mapkey string
var mapvalue string
for iNdEx < postIndex {
entryPreIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
if fieldNum == 1 {
var stringLenmapkey uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapkey |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapkey := int(stringLenmapkey)
if intStringLenmapkey < 0 {
return ErrInvalidLength
}
postStringIndexmapkey := iNdEx + intStringLenmapkey
if postStringIndexmapkey < 0 {
return ErrInvalidLength
}
if postStringIndexmapkey > l {
return io.ErrUnexpectedEOF
}
mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
iNdEx = postStringIndexmapkey
} else if fieldNum == 2 {
var stringLenmapvalue uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapvalue |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapvalue := int(stringLenmapvalue)
if intStringLenmapvalue < 0 {
return ErrInvalidLength
}
postStringIndexmapvalue := iNdEx + intStringLenmapvalue
if postStringIndexmapvalue < 0 {
return ErrInvalidLength
}
if postStringIndexmapvalue > l {
return io.ErrUnexpectedEOF
}
mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
iNdEx = postStringIndexmapvalue
} else {
iNdEx = entryPreIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > postIndex {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
m.Config[mapkey] = mapvalue
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skip(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLength
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLength
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
)
+169
View File
@@ -0,0 +1,169 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// ConfigGetRequest is the request type for Config.Get.
type ConfigGetRequest struct {
Key string `json:"key"`
}
// ConfigGetResponse is the response type for Config.Get.
type ConfigGetResponse struct {
Value string `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
}
// ConfigGetIntRequest is the request type for Config.GetInt.
type ConfigGetIntRequest struct {
Key string `json:"key"`
}
// ConfigGetIntResponse is the response type for Config.GetInt.
type ConfigGetIntResponse struct {
Value int64 `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
}
// ConfigKeysRequest is the request type for Config.Keys.
type ConfigKeysRequest struct {
Prefix string `json:"prefix"`
}
// ConfigKeysResponse is the response type for Config.Keys.
type ConfigKeysResponse struct {
Keys []string `json:"keys,omitempty"`
}
// RegisterConfigHostFunctions registers Config service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterConfigHostFunctions(service ConfigService) []extism.HostFunction {
return []extism.HostFunction{
newConfigGetHostFunction(service),
newConfigGetIntHostFunction(service),
newConfigKeysHostFunction(service),
}
}
func newConfigGetHostFunction(service ConfigService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"config_get",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
configWriteError(p, stack, err)
return
}
var req ConfigGetRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
configWriteError(p, stack, err)
return
}
// Call the service method
value, exists := service.Get(ctx, req.Key)
// Write JSON response to plugin memory
resp := ConfigGetResponse{
Value: value,
Exists: exists,
}
configWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newConfigGetIntHostFunction(service ConfigService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"config_getint",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
configWriteError(p, stack, err)
return
}
var req ConfigGetIntRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
configWriteError(p, stack, err)
return
}
// Call the service method
value, exists := service.GetInt(ctx, req.Key)
// Write JSON response to plugin memory
resp := ConfigGetIntResponse{
Value: value,
Exists: exists,
}
configWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newConfigKeysHostFunction(service ConfigService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"config_keys",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
configWriteError(p, stack, err)
return
}
var req ConfigKeysRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
configWriteError(p, stack, err)
return
}
// Call the service method
keys := service.Keys(ctx, req.Prefix)
// Write JSON response to plugin memory
resp := ConfigKeysResponse{
Keys: keys,
}
configWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// configWriteResponse writes a JSON response to plugin memory.
func configWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
configWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// configWriteError writes an error response to plugin memory.
func configWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+39
View File
@@ -0,0 +1,39 @@
// Package host provides host services that can be called by plugins via Extism host functions.
//
// Host services allow plugins to access Navidrome functionality like the Subsonic API,
// scheduler, and other internal services. Services are defined as Go interfaces with
// special annotations that enable automatic code generation of Extism host function wrappers.
//
// # Annotation Format
//
// Host services use Go doc comment annotations to mark interfaces and methods for code generation:
//
// // MyService provides some functionality.
// //nd:hostservice name=MyService permission=myservice
// type MyService interface {
// // DoSomething performs an action.
// //nd:hostfunc
// DoSomething(ctx context.Context, input string) (output string, err error)
// }
//
// Service-level annotations:
// - //nd:hostservice - Marks an interface as a host service
// - name=<ServiceName> - Service identifier used in generated code
// - permission=<key> - Manifest permission key (e.g., "subsonicapi", "scheduler")
//
// Method-level annotations:
// - //nd:hostfunc - Marks a method for host function wrapper generation
// - name=<CustomName> - Optional: override the export name
//
// # Generated Code
//
// The ndpgen tool reads annotated interfaces and generates Extism host function wrappers
// that handle:
// - JSON serialization/deserialization of request/response types
// - Memory operations (ReadBytes, WriteBytes, Alloc)
// - Error handling and propagation
// - Service registration functions
//
// Generated files follow the pattern <servicename>_gen.go and include a header comment
// indicating they should not be edited manually.
package host
-117
View File
@@ -1,117 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/http/http.proto
package http
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type HttpRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
TimeoutMs int32 `protobuf:"varint,3,opt,name=timeout_ms,json=timeoutMs,proto3" json:"timeout_ms,omitempty"`
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` // Ignored for GET/DELETE/HEAD/OPTIONS
}
func (x *HttpRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *HttpRequest) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *HttpRequest) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (x *HttpRequest) GetTimeoutMs() int32 {
if x != nil {
return x.TimeoutMs
}
return 0
}
func (x *HttpRequest) GetBody() []byte {
if x != nil {
return x.Body
}
return nil
}
type HttpResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"`
Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` // Non-empty if network/protocol error
}
func (x *HttpResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *HttpResponse) GetStatus() int32 {
if x != nil {
return x.Status
}
return 0
}
func (x *HttpResponse) GetBody() []byte {
if x != nil {
return x.Body
}
return nil
}
func (x *HttpResponse) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (x *HttpResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
// go:plugin type=host version=1
type HttpService interface {
Get(context.Context, *HttpRequest) (*HttpResponse, error)
Post(context.Context, *HttpRequest) (*HttpResponse, error)
Put(context.Context, *HttpRequest) (*HttpResponse, error)
Delete(context.Context, *HttpRequest) (*HttpResponse, error)
Patch(context.Context, *HttpRequest) (*HttpResponse, error)
Head(context.Context, *HttpRequest) (*HttpResponse, error)
Options(context.Context, *HttpRequest) (*HttpResponse, error)
}
-30
View File
@@ -1,30 +0,0 @@
syntax = "proto3";
package http;
option go_package = "github.com/navidrome/navidrome/plugins/host/http;http";
// go:plugin type=host version=1
service HttpService {
rpc Get(HttpRequest) returns (HttpResponse);
rpc Post(HttpRequest) returns (HttpResponse);
rpc Put(HttpRequest) returns (HttpResponse);
rpc Delete(HttpRequest) returns (HttpResponse);
rpc Patch(HttpRequest) returns (HttpResponse);
rpc Head(HttpRequest) returns (HttpResponse);
rpc Options(HttpRequest) returns (HttpResponse);
}
message HttpRequest {
string url = 1;
map<string, string> headers = 2;
int32 timeout_ms = 3;
bytes body = 4; // Ignored for GET/DELETE/HEAD/OPTIONS
}
message HttpResponse {
int32 status = 1;
bytes body = 2;
map<string, string> headers = 3;
string error = 4; // Non-empty if network/protocol error
}
-258
View File
@@ -1,258 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/http/http.proto
package http
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _httpService struct {
HttpService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions HttpService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _httpService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Get), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("get")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Post), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("post")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Put), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("put")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Delete), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("delete")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Patch), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("patch")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Head), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("head")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Options), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("options")
_, err := envBuilder.Instantiate(ctx)
return err
}
func (h _httpService) _Get(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Get(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _httpService) _Post(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Post(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _httpService) _Put(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Put(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _httpService) _Delete(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Delete(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _httpService) _Patch(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Patch(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _httpService) _Head(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Head(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
func (h _httpService) _Options(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(HttpRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Options(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
-182
View File
@@ -1,182 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/http/http.proto
package http
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type httpService struct{}
func NewHttpService() HttpService {
return httpService{}
}
//go:wasmimport env get
func _get(ptr uint32, size uint32) uint64
func (h httpService) Get(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _get(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env post
func _post(ptr uint32, size uint32) uint64
func (h httpService) Post(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _post(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env put
func _put(ptr uint32, size uint32) uint64
func (h httpService) Put(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _put(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env delete
func _delete(ptr uint32, size uint32) uint64
func (h httpService) Delete(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _delete(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env patch
func _patch(ptr uint32, size uint32) uint64
func (h httpService) Patch(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _patch(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env head
func _head(ptr uint32, size uint32) uint64
func (h httpService) Head(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _head(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env options
func _options(ptr uint32, size uint32) uint64
func (h httpService) Options(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _options(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(HttpResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
-7
View File
@@ -1,7 +0,0 @@
//go:build !wasip1
package http
func NewHttpService() HttpService {
panic("not implemented")
}
-850
View File
@@ -1,850 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/http/http.proto
package http
import (
fmt "fmt"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
bits "math/bits"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *HttpRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HttpRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *HttpRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Body) > 0 {
i -= len(m.Body)
copy(dAtA[i:], m.Body)
i = encodeVarint(dAtA, i, uint64(len(m.Body)))
i--
dAtA[i] = 0x22
}
if m.TimeoutMs != 0 {
i = encodeVarint(dAtA, i, uint64(m.TimeoutMs))
i--
dAtA[i] = 0x18
}
if len(m.Headers) > 0 {
for k := range m.Headers {
v := m.Headers[k]
baseI := i
i -= len(v)
copy(dAtA[i:], v)
i = encodeVarint(dAtA, i, uint64(len(v)))
i--
dAtA[i] = 0x12
i -= len(k)
copy(dAtA[i:], k)
i = encodeVarint(dAtA, i, uint64(len(k)))
i--
dAtA[i] = 0xa
i = encodeVarint(dAtA, i, uint64(baseI-i))
i--
dAtA[i] = 0x12
}
}
if len(m.Url) > 0 {
i -= len(m.Url)
copy(dAtA[i:], m.Url)
i = encodeVarint(dAtA, i, uint64(len(m.Url)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *HttpResponse) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HttpResponse) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *HttpResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Error) > 0 {
i -= len(m.Error)
copy(dAtA[i:], m.Error)
i = encodeVarint(dAtA, i, uint64(len(m.Error)))
i--
dAtA[i] = 0x22
}
if len(m.Headers) > 0 {
for k := range m.Headers {
v := m.Headers[k]
baseI := i
i -= len(v)
copy(dAtA[i:], v)
i = encodeVarint(dAtA, i, uint64(len(v)))
i--
dAtA[i] = 0x12
i -= len(k)
copy(dAtA[i:], k)
i = encodeVarint(dAtA, i, uint64(len(k)))
i--
dAtA[i] = 0xa
i = encodeVarint(dAtA, i, uint64(baseI-i))
i--
dAtA[i] = 0x1a
}
}
if len(m.Body) > 0 {
i -= len(m.Body)
copy(dAtA[i:], m.Body)
i = encodeVarint(dAtA, i, uint64(len(m.Body)))
i--
dAtA[i] = 0x12
}
if m.Status != 0 {
i = encodeVarint(dAtA, i, uint64(m.Status))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func encodeVarint(dAtA []byte, offset int, v uint64) int {
offset -= sov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *HttpRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Url)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if len(m.Headers) > 0 {
for k, v := range m.Headers {
_ = k
_ = v
mapEntrySize := 1 + len(k) + sov(uint64(len(k))) + 1 + len(v) + sov(uint64(len(v)))
n += mapEntrySize + 1 + sov(uint64(mapEntrySize))
}
}
if m.TimeoutMs != 0 {
n += 1 + sov(uint64(m.TimeoutMs))
}
l = len(m.Body)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *HttpResponse) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Status != 0 {
n += 1 + sov(uint64(m.Status))
}
l = len(m.Body)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if len(m.Headers) > 0 {
for k, v := range m.Headers {
_ = k
_ = v
mapEntrySize := 1 + len(k) + sov(uint64(len(k))) + 1 + len(v) + sov(uint64(len(v)))
n += mapEntrySize + 1 + sov(uint64(mapEntrySize))
}
}
l = len(m.Error)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
n += len(m.unknownFields)
return n
}
func sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func soz(x uint64) (n int) {
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *HttpRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HttpRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HttpRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Url = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Headers", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Headers == nil {
m.Headers = make(map[string]string)
}
var mapkey string
var mapvalue string
for iNdEx < postIndex {
entryPreIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
if fieldNum == 1 {
var stringLenmapkey uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapkey |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapkey := int(stringLenmapkey)
if intStringLenmapkey < 0 {
return ErrInvalidLength
}
postStringIndexmapkey := iNdEx + intStringLenmapkey
if postStringIndexmapkey < 0 {
return ErrInvalidLength
}
if postStringIndexmapkey > l {
return io.ErrUnexpectedEOF
}
mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
iNdEx = postStringIndexmapkey
} else if fieldNum == 2 {
var stringLenmapvalue uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapvalue |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapvalue := int(stringLenmapvalue)
if intStringLenmapvalue < 0 {
return ErrInvalidLength
}
postStringIndexmapvalue := iNdEx + intStringLenmapvalue
if postStringIndexmapvalue < 0 {
return ErrInvalidLength
}
if postStringIndexmapvalue > l {
return io.ErrUnexpectedEOF
}
mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
iNdEx = postStringIndexmapvalue
} else {
iNdEx = entryPreIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > postIndex {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
m.Headers[mapkey] = mapvalue
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field TimeoutMs", wireType)
}
m.TimeoutMs = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.TimeoutMs |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Body = append(m.Body[:0], dAtA[iNdEx:postIndex]...)
if m.Body == nil {
m.Body = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *HttpResponse) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HttpResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HttpResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType)
}
m.Status = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Status |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Body = append(m.Body[:0], dAtA[iNdEx:postIndex]...)
if m.Body == nil {
m.Body = []byte{}
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Headers", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Headers == nil {
m.Headers = make(map[string]string)
}
var mapkey string
var mapvalue string
for iNdEx < postIndex {
entryPreIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
if fieldNum == 1 {
var stringLenmapkey uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapkey |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapkey := int(stringLenmapkey)
if intStringLenmapkey < 0 {
return ErrInvalidLength
}
postStringIndexmapkey := iNdEx + intStringLenmapkey
if postStringIndexmapkey < 0 {
return ErrInvalidLength
}
if postStringIndexmapkey > l {
return io.ErrUnexpectedEOF
}
mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
iNdEx = postStringIndexmapkey
} else if fieldNum == 2 {
var stringLenmapvalue uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapvalue |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapvalue := int(stringLenmapvalue)
if intStringLenmapvalue < 0 {
return ErrInvalidLength
}
postStringIndexmapvalue := iNdEx + intStringLenmapvalue
if postStringIndexmapvalue < 0 {
return ErrInvalidLength
}
if postStringIndexmapvalue > l {
return io.ErrUnexpectedEOF
}
mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
iNdEx = postStringIndexmapvalue
} else {
iNdEx = entryPreIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > postIndex {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
m.Headers[mapkey] = mapvalue
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Error = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skip(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLength
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLength
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
)
+65
View File
@@ -0,0 +1,65 @@
package host
import "context"
// KVStoreService provides persistent key-value storage for plugins.
//
// Unlike CacheService which is in-memory only, KVStoreService persists data
// to disk and survives server restarts. Each plugin has its own isolated
// storage with configurable size limits.
//
// Values are stored as raw bytes, giving plugins full control over
// serialization (JSON, protobuf, etc.).
//
//nd:hostservice name=KVStore permission=kvstore
type KVStoreService interface {
// Set stores a byte value with the given key.
//
// Parameters:
// - key: The storage key (max 256 bytes, UTF-8)
// - value: The byte slice to store
//
// Returns an error if the storage limit would be exceeded or the operation fails.
//nd:hostfunc
Set(ctx context.Context, key string, value []byte) error
// Get retrieves a byte value from storage.
//
// Parameters:
// - key: The storage key
//
// Returns the value and whether the key exists.
//nd:hostfunc
Get(ctx context.Context, key string) (value []byte, exists bool, err error)
// Delete removes a value from storage.
//
// Parameters:
// - key: The storage key
//
// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
//nd:hostfunc
Delete(ctx context.Context, key string) error
// Has checks if a key exists in storage.
//
// Parameters:
// - key: The storage key
//
// Returns true if the key exists.
//nd:hostfunc
Has(ctx context.Context, key string) (exists bool, err error)
// List returns all keys matching the given prefix.
//
// Parameters:
// - prefix: Key prefix to filter by (empty string returns all keys)
//
// Returns a slice of matching keys.
//nd:hostfunc
List(ctx context.Context, prefix string) (keys []string, err error)
// GetStorageUsed returns the total storage used by this plugin in bytes.
//nd:hostfunc
GetStorageUsed(ctx context.Context) (bytes int64, err error)
}
+297
View File
@@ -0,0 +1,297 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// KVStoreSetRequest is the request type for KVStore.Set.
type KVStoreSetRequest struct {
Key string `json:"key"`
Value []byte `json:"value"`
}
// KVStoreSetResponse is the response type for KVStore.Set.
type KVStoreSetResponse struct {
Error string `json:"error,omitempty"`
}
// KVStoreGetRequest is the request type for KVStore.Get.
type KVStoreGetRequest struct {
Key string `json:"key"`
}
// KVStoreGetResponse is the response type for KVStore.Get.
type KVStoreGetResponse struct {
Value []byte `json:"value,omitempty"`
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// KVStoreDeleteRequest is the request type for KVStore.Delete.
type KVStoreDeleteRequest struct {
Key string `json:"key"`
}
// KVStoreDeleteResponse is the response type for KVStore.Delete.
type KVStoreDeleteResponse struct {
Error string `json:"error,omitempty"`
}
// KVStoreHasRequest is the request type for KVStore.Has.
type KVStoreHasRequest struct {
Key string `json:"key"`
}
// KVStoreHasResponse is the response type for KVStore.Has.
type KVStoreHasResponse struct {
Exists bool `json:"exists,omitempty"`
Error string `json:"error,omitempty"`
}
// KVStoreListRequest is the request type for KVStore.List.
type KVStoreListRequest struct {
Prefix string `json:"prefix"`
}
// KVStoreListResponse is the response type for KVStore.List.
type KVStoreListResponse struct {
Keys []string `json:"keys,omitempty"`
Error string `json:"error,omitempty"`
}
// KVStoreGetStorageUsedResponse is the response type for KVStore.GetStorageUsed.
type KVStoreGetStorageUsedResponse struct {
Bytes int64 `json:"bytes,omitempty"`
Error string `json:"error,omitempty"`
}
// RegisterKVStoreHostFunctions registers KVStore service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterKVStoreHostFunctions(service KVStoreService) []extism.HostFunction {
return []extism.HostFunction{
newKVStoreSetHostFunction(service),
newKVStoreGetHostFunction(service),
newKVStoreDeleteHostFunction(service),
newKVStoreHasHostFunction(service),
newKVStoreListHostFunction(service),
newKVStoreGetStorageUsedHostFunction(service),
}
}
func newKVStoreSetHostFunction(service KVStoreService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"kvstore_set",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
kvstoreWriteError(p, stack, err)
return
}
var req KVStoreSetRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
kvstoreWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.Set(ctx, req.Key, req.Value); svcErr != nil {
kvstoreWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := KVStoreSetResponse{}
kvstoreWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newKVStoreGetHostFunction(service KVStoreService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"kvstore_get",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
kvstoreWriteError(p, stack, err)
return
}
var req KVStoreGetRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
kvstoreWriteError(p, stack, err)
return
}
// Call the service method
value, exists, svcErr := service.Get(ctx, req.Key)
if svcErr != nil {
kvstoreWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := KVStoreGetResponse{
Value: value,
Exists: exists,
}
kvstoreWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newKVStoreDeleteHostFunction(service KVStoreService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"kvstore_delete",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
kvstoreWriteError(p, stack, err)
return
}
var req KVStoreDeleteRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
kvstoreWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.Delete(ctx, req.Key); svcErr != nil {
kvstoreWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := KVStoreDeleteResponse{}
kvstoreWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newKVStoreHasHostFunction(service KVStoreService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"kvstore_has",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
kvstoreWriteError(p, stack, err)
return
}
var req KVStoreHasRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
kvstoreWriteError(p, stack, err)
return
}
// Call the service method
exists, svcErr := service.Has(ctx, req.Key)
if svcErr != nil {
kvstoreWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := KVStoreHasResponse{
Exists: exists,
}
kvstoreWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newKVStoreListHostFunction(service KVStoreService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"kvstore_list",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
kvstoreWriteError(p, stack, err)
return
}
var req KVStoreListRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
kvstoreWriteError(p, stack, err)
return
}
// Call the service method
keys, svcErr := service.List(ctx, req.Prefix)
if svcErr != nil {
kvstoreWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := KVStoreListResponse{
Keys: keys,
}
kvstoreWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newKVStoreGetStorageUsedHostFunction(service KVStoreService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"kvstore_getstorageused",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Call the service method
bytes, svcErr := service.GetStorageUsed(ctx)
if svcErr != nil {
kvstoreWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := KVStoreGetStorageUsedResponse{
Bytes: bytes,
}
kvstoreWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// kvstoreWriteResponse writes a JSON response to plugin memory.
func kvstoreWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
kvstoreWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// kvstoreWriteError writes an error response to plugin memory.
func kvstoreWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+41
View File
@@ -0,0 +1,41 @@
package host
import "context"
// Library represents a music library with metadata.
type Library struct {
ID int32 `json:"id"`
Name string `json:"name"`
Path string `json:"path,omitempty"`
MountPoint string `json:"mountPoint,omitempty"`
LastScanAt int64 `json:"lastScanAt"`
TotalSongs int32 `json:"totalSongs"`
TotalAlbums int32 `json:"totalAlbums"`
TotalArtists int32 `json:"totalArtists"`
TotalSize int64 `json:"totalSize"`
TotalDuration float64 `json:"totalDuration"`
}
// LibraryService provides access to music library metadata for plugins.
//
// This service allows plugins to query information about configured music libraries,
// including statistics and optionally filesystem access to library directories.
// Filesystem access is controlled via the `filesystem` permission flag.
//
//nd:hostservice name=Library permission=library
type LibraryService interface {
// GetLibrary retrieves metadata for a specific library by ID.
//
// Parameters:
// - id: The library's unique identifier
//
// Returns the library metadata, or an error if the library is not found.
//nd:hostfunc
GetLibrary(ctx context.Context, id int32) (*Library, error)
// GetAllLibraries retrieves metadata for all configured libraries.
//
// Returns a slice of all libraries with their metadata.
//nd:hostfunc
GetAllLibraries(ctx context.Context) ([]Library, error)
}
+118
View File
@@ -0,0 +1,118 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// LibraryGetLibraryRequest is the request type for Library.GetLibrary.
type LibraryGetLibraryRequest struct {
Id int32 `json:"id"`
}
// LibraryGetLibraryResponse is the response type for Library.GetLibrary.
type LibraryGetLibraryResponse struct {
Result *Library `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
// LibraryGetAllLibrariesResponse is the response type for Library.GetAllLibraries.
type LibraryGetAllLibrariesResponse struct {
Result []Library `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
// RegisterLibraryHostFunctions registers Library service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterLibraryHostFunctions(service LibraryService) []extism.HostFunction {
return []extism.HostFunction{
newLibraryGetLibraryHostFunction(service),
newLibraryGetAllLibrariesHostFunction(service),
}
}
func newLibraryGetLibraryHostFunction(service LibraryService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"library_getlibrary",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
libraryWriteError(p, stack, err)
return
}
var req LibraryGetLibraryRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
libraryWriteError(p, stack, err)
return
}
// Call the service method
result, svcErr := service.GetLibrary(ctx, req.Id)
if svcErr != nil {
libraryWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := LibraryGetLibraryResponse{
Result: result,
}
libraryWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newLibraryGetAllLibrariesHostFunction(service LibraryService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"library_getalllibraries",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Call the service method
result, svcErr := service.GetAllLibraries(ctx)
if svcErr != nil {
libraryWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := LibraryGetAllLibrariesResponse{
Result: result,
}
libraryWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// libraryWriteResponse writes a JSON response to plugin memory.
func libraryWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
libraryWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// libraryWriteError writes an error response to plugin memory.
func libraryWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+44
View File
@@ -0,0 +1,44 @@
package host
import "context"
// SchedulerService provides task scheduling capabilities for plugins.
//
// This service allows plugins to schedule both one-time and recurring tasks using
// cron expressions. All scheduled tasks can be cancelled using their schedule ID.
//
//nd:hostservice name=Scheduler permission=scheduler
type SchedulerService interface {
// ScheduleOneTime schedules a one-time event to be triggered after the specified delay.
// Plugins that use this function must also implement the SchedulerCallback capability
//
// Parameters:
// - delaySeconds: Number of seconds to wait before triggering the event
// - payload: Data to be passed to the scheduled event handler
// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
//
// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
//nd:hostfunc
ScheduleOneTime(ctx context.Context, delaySeconds int32, payload string, scheduleID string) (newScheduleID string, err error)
// ScheduleRecurring schedules a recurring event using a cron expression.
// Plugins that use this function must also implement the SchedulerCallback capability
//
// Parameters:
// - cronExpression: Standard cron format expression (e.g., "0 0 * * *" for daily at midnight)
// - payload: Data to be passed to each scheduled event handler invocation
// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
//
// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
//nd:hostfunc
ScheduleRecurring(ctx context.Context, cronExpression string, payload string, scheduleID string) (newScheduleID string, err error)
// CancelSchedule cancels a scheduled job identified by its schedule ID.
//
// This works for both one-time and recurring schedules. Once cancelled, the job will not trigger
// any future events.
//
// Returns an error if the schedule ID is not found or if cancellation fails.
//nd:hostfunc
CancelSchedule(ctx context.Context, scheduleID string) error
}
-212
View File
@@ -1,212 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/scheduler/scheduler.proto
package scheduler
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ScheduleOneTimeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DelaySeconds int32 `protobuf:"varint,1,opt,name=delay_seconds,json=delaySeconds,proto3" json:"delay_seconds,omitempty"` // Delay in seconds
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // Serialized data to pass to the callback
ScheduleId string `protobuf:"bytes,3,opt,name=schedule_id,json=scheduleId,proto3" json:"schedule_id,omitempty"` // Optional custom ID (if not provided, one will be generated)
}
func (x *ScheduleOneTimeRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *ScheduleOneTimeRequest) GetDelaySeconds() int32 {
if x != nil {
return x.DelaySeconds
}
return 0
}
func (x *ScheduleOneTimeRequest) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
func (x *ScheduleOneTimeRequest) GetScheduleId() string {
if x != nil {
return x.ScheduleId
}
return ""
}
type ScheduleRecurringRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
CronExpression string `protobuf:"bytes,1,opt,name=cron_expression,json=cronExpression,proto3" json:"cron_expression,omitempty"` // Cron expression (e.g. "0 0 * * *" for daily at midnight)
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // Serialized data to pass to the callback
ScheduleId string `protobuf:"bytes,3,opt,name=schedule_id,json=scheduleId,proto3" json:"schedule_id,omitempty"` // Optional custom ID (if not provided, one will be generated)
}
func (x *ScheduleRecurringRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *ScheduleRecurringRequest) GetCronExpression() string {
if x != nil {
return x.CronExpression
}
return ""
}
func (x *ScheduleRecurringRequest) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
func (x *ScheduleRecurringRequest) GetScheduleId() string {
if x != nil {
return x.ScheduleId
}
return ""
}
type ScheduleResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ScheduleId string `protobuf:"bytes,1,opt,name=schedule_id,json=scheduleId,proto3" json:"schedule_id,omitempty"` // ID to reference this scheduled job
}
func (x *ScheduleResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *ScheduleResponse) GetScheduleId() string {
if x != nil {
return x.ScheduleId
}
return ""
}
type CancelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ScheduleId string `protobuf:"bytes,1,opt,name=schedule_id,json=scheduleId,proto3" json:"schedule_id,omitempty"` // ID of the schedule to cancel
}
func (x *CancelRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *CancelRequest) GetScheduleId() string {
if x != nil {
return x.ScheduleId
}
return ""
}
type CancelResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // Whether cancellation was successful
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` // Error message if cancellation failed
}
func (x *CancelResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *CancelResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
func (x *CancelResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type TimeNowRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *TimeNowRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
type TimeNowResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Rfc3339Nano string `protobuf:"bytes,1,opt,name=rfc3339_nano,json=rfc3339Nano,proto3" json:"rfc3339_nano,omitempty"` // Current time in RFC3339Nano format
UnixMilli int64 `protobuf:"varint,2,opt,name=unix_milli,json=unixMilli,proto3" json:"unix_milli,omitempty"` // Current time as Unix milliseconds timestamp
LocalTimeZone string `protobuf:"bytes,3,opt,name=local_time_zone,json=localTimeZone,proto3" json:"local_time_zone,omitempty"` // Local timezone name (e.g., "America/New_York", "UTC")
}
func (x *TimeNowResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *TimeNowResponse) GetRfc3339Nano() string {
if x != nil {
return x.Rfc3339Nano
}
return ""
}
func (x *TimeNowResponse) GetUnixMilli() int64 {
if x != nil {
return x.UnixMilli
}
return 0
}
func (x *TimeNowResponse) GetLocalTimeZone() string {
if x != nil {
return x.LocalTimeZone
}
return ""
}
// go:plugin type=host version=1
type SchedulerService interface {
// One-time event scheduling
ScheduleOneTime(context.Context, *ScheduleOneTimeRequest) (*ScheduleResponse, error)
// Recurring event scheduling
ScheduleRecurring(context.Context, *ScheduleRecurringRequest) (*ScheduleResponse, error)
// Cancel any scheduled job
CancelSchedule(context.Context, *CancelRequest) (*CancelResponse, error)
// Get current time in multiple formats
TimeNow(context.Context, *TimeNowRequest) (*TimeNowResponse, error)
}
-55
View File
@@ -1,55 +0,0 @@
syntax = "proto3";
package scheduler;
option go_package = "github.com/navidrome/navidrome/plugins/host/scheduler;scheduler";
// go:plugin type=host version=1
service SchedulerService {
// One-time event scheduling
rpc ScheduleOneTime(ScheduleOneTimeRequest) returns (ScheduleResponse);
// Recurring event scheduling
rpc ScheduleRecurring(ScheduleRecurringRequest) returns (ScheduleResponse);
// Cancel any scheduled job
rpc CancelSchedule(CancelRequest) returns (CancelResponse);
// Get current time in multiple formats
rpc TimeNow(TimeNowRequest) returns (TimeNowResponse);
}
message ScheduleOneTimeRequest {
int32 delay_seconds = 1; // Delay in seconds
bytes payload = 2; // Serialized data to pass to the callback
string schedule_id = 3; // Optional custom ID (if not provided, one will be generated)
}
message ScheduleRecurringRequest {
string cron_expression = 1; // Cron expression (e.g. "0 0 * * *" for daily at midnight)
bytes payload = 2; // Serialized data to pass to the callback
string schedule_id = 3; // Optional custom ID (if not provided, one will be generated)
}
message ScheduleResponse {
string schedule_id = 1; // ID to reference this scheduled job
}
message CancelRequest {
string schedule_id = 1; // ID of the schedule to cancel
}
message CancelResponse {
bool success = 1; // Whether cancellation was successful
string error = 2; // Error message if cancellation failed
}
message TimeNowRequest {
// Empty request - no parameters needed
}
message TimeNowResponse {
string rfc3339_nano = 1; // Current time in RFC3339Nano format
int64 unix_milli = 2; // Current time as Unix milliseconds timestamp
string local_time_zone = 3; // Local timezone name (e.g., "America/New_York", "UTC")
}
-170
View File
@@ -1,170 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/scheduler/scheduler.proto
package scheduler
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _schedulerService struct {
SchedulerService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions SchedulerService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _schedulerService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._ScheduleOneTime), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("schedule_one_time")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._ScheduleRecurring), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("schedule_recurring")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._CancelSchedule), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("cancel_schedule")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._TimeNow), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("time_now")
_, err := envBuilder.Instantiate(ctx)
return err
}
// One-time event scheduling
func (h _schedulerService) _ScheduleOneTime(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(ScheduleOneTimeRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.ScheduleOneTime(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Recurring event scheduling
func (h _schedulerService) _ScheduleRecurring(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(ScheduleRecurringRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.ScheduleRecurring(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Cancel any scheduled job
func (h _schedulerService) _CancelSchedule(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(CancelRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.CancelSchedule(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Get current time in multiple formats
func (h _schedulerService) _TimeNow(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(TimeNowRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.TimeNow(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
@@ -1,113 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/scheduler/scheduler.proto
package scheduler
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type schedulerService struct{}
func NewSchedulerService() SchedulerService {
return schedulerService{}
}
//go:wasmimport env schedule_one_time
func _schedule_one_time(ptr uint32, size uint32) uint64
func (h schedulerService) ScheduleOneTime(ctx context.Context, request *ScheduleOneTimeRequest) (*ScheduleResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _schedule_one_time(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(ScheduleResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env schedule_recurring
func _schedule_recurring(ptr uint32, size uint32) uint64
func (h schedulerService) ScheduleRecurring(ctx context.Context, request *ScheduleRecurringRequest) (*ScheduleResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _schedule_recurring(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(ScheduleResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env cancel_schedule
func _cancel_schedule(ptr uint32, size uint32) uint64
func (h schedulerService) CancelSchedule(ctx context.Context, request *CancelRequest) (*CancelResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _cancel_schedule(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(CancelResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env time_now
func _time_now(ptr uint32, size uint32) uint64
func (h schedulerService) TimeNow(ctx context.Context, request *TimeNowRequest) (*TimeNowResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _time_now(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(TimeNowResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
@@ -1,7 +0,0 @@
//go:build !wasip1
package scheduler
func NewSchedulerService() SchedulerService {
panic("not implemented")
}
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime.
type SchedulerScheduleOneTimeRequest struct {
DelaySeconds int32 `json:"delaySeconds"`
Payload string `json:"payload"`
ScheduleID string `json:"scheduleId"`
}
// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime.
type SchedulerScheduleOneTimeResponse struct {
NewScheduleID string `json:"newScheduleId,omitempty"`
Error string `json:"error,omitempty"`
}
// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring.
type SchedulerScheduleRecurringRequest struct {
CronExpression string `json:"cronExpression"`
Payload string `json:"payload"`
ScheduleID string `json:"scheduleId"`
}
// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring.
type SchedulerScheduleRecurringResponse struct {
NewScheduleID string `json:"newScheduleId,omitempty"`
Error string `json:"error,omitempty"`
}
// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule.
type SchedulerCancelScheduleRequest struct {
ScheduleID string `json:"scheduleId"`
}
// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule.
type SchedulerCancelScheduleResponse struct {
Error string `json:"error,omitempty"`
}
// RegisterSchedulerHostFunctions registers Scheduler service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterSchedulerHostFunctions(service SchedulerService) []extism.HostFunction {
return []extism.HostFunction{
newSchedulerScheduleOneTimeHostFunction(service),
newSchedulerScheduleRecurringHostFunction(service),
newSchedulerCancelScheduleHostFunction(service),
}
}
func newSchedulerScheduleOneTimeHostFunction(service SchedulerService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"scheduler_scheduleonetime",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
schedulerWriteError(p, stack, err)
return
}
var req SchedulerScheduleOneTimeRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
schedulerWriteError(p, stack, err)
return
}
// Call the service method
newscheduleid, svcErr := service.ScheduleOneTime(ctx, req.DelaySeconds, req.Payload, req.ScheduleID)
if svcErr != nil {
schedulerWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := SchedulerScheduleOneTimeResponse{
NewScheduleID: newscheduleid,
}
schedulerWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newSchedulerScheduleRecurringHostFunction(service SchedulerService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"scheduler_schedulerecurring",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
schedulerWriteError(p, stack, err)
return
}
var req SchedulerScheduleRecurringRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
schedulerWriteError(p, stack, err)
return
}
// Call the service method
newscheduleid, svcErr := service.ScheduleRecurring(ctx, req.CronExpression, req.Payload, req.ScheduleID)
if svcErr != nil {
schedulerWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := SchedulerScheduleRecurringResponse{
NewScheduleID: newscheduleid,
}
schedulerWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newSchedulerCancelScheduleHostFunction(service SchedulerService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"scheduler_cancelschedule",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
schedulerWriteError(p, stack, err)
return
}
var req SchedulerCancelScheduleRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
schedulerWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.CancelSchedule(ctx, req.ScheduleID); svcErr != nil {
schedulerWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := SchedulerCancelScheduleResponse{}
schedulerWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// schedulerWriteResponse writes a JSON response to plugin memory.
func schedulerWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
schedulerWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// schedulerWriteError writes an error response to plugin memory.
func schedulerWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+18
View File
@@ -0,0 +1,18 @@
package host
import "context"
// SubsonicAPIService provides access to Navidrome's Subsonic API from plugins.
//
// This service allows plugins to make Subsonic API requests on behalf of the plugin's user,
// enabling access to library data, user preferences, and other Subsonic-compatible operations.
//
//nd:hostservice name=SubsonicAPI permission=subsonicapi
type SubsonicAPIService interface {
// Call executes a Subsonic API request and returns the JSON response.
//
// The uri parameter should be the Subsonic API path without the server prefix,
// e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON.
//nd:hostfunc
Call(ctx context.Context, uri string) (responseJSON string, err error)
}
@@ -1,71 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/subsonicapi/subsonicapi.proto
package subsonicapi
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type CallRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
}
func (x *CallRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *CallRequest) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
type CallResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Json string `protobuf:"bytes,1,opt,name=json,proto3" json:"json,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` // Non-empty if operation failed
}
func (x *CallResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *CallResponse) GetJson() string {
if x != nil {
return x.Json
}
return ""
}
func (x *CallResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
// go:plugin type=host version=1
type SubsonicAPIService interface {
Call(context.Context, *CallRequest) (*CallResponse, error)
}
@@ -1,19 +0,0 @@
syntax = "proto3";
package subsonicapi;
option go_package = "github.com/navidrome/navidrome/plugins/host/subsonicapi;subsonicapi";
// go:plugin type=host version=1
service SubsonicAPIService {
rpc Call(CallRequest) returns (CallResponse);
}
message CallRequest {
string url = 1;
}
message CallResponse {
string json = 1;
string error = 2; // Non-empty if operation failed
}
@@ -1,66 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/subsonicapi/subsonicapi.proto
package subsonicapi
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _subsonicAPIService struct {
SubsonicAPIService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions SubsonicAPIService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _subsonicAPIService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Call), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("call")
_, err := envBuilder.Instantiate(ctx)
return err
}
func (h _subsonicAPIService) _Call(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(CallRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Call(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
@@ -1,44 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/subsonicapi/subsonicapi.proto
package subsonicapi
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type subsonicAPIService struct{}
func NewSubsonicAPIService() SubsonicAPIService {
return subsonicAPIService{}
}
//go:wasmimport env call
func _call(ptr uint32, size uint32) uint64
func (h subsonicAPIService) Call(ctx context.Context, request *CallRequest) (*CallResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _call(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(CallResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
@@ -1,441 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/subsonicapi/subsonicapi.proto
package subsonicapi
import (
fmt "fmt"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
bits "math/bits"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *CallRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CallRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *CallRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Url) > 0 {
i -= len(m.Url)
copy(dAtA[i:], m.Url)
i = encodeVarint(dAtA, i, uint64(len(m.Url)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *CallResponse) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CallResponse) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *CallResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Error) > 0 {
i -= len(m.Error)
copy(dAtA[i:], m.Error)
i = encodeVarint(dAtA, i, uint64(len(m.Error)))
i--
dAtA[i] = 0x12
}
if len(m.Json) > 0 {
i -= len(m.Json)
copy(dAtA[i:], m.Json)
i = encodeVarint(dAtA, i, uint64(len(m.Json)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarint(dAtA []byte, offset int, v uint64) int {
offset -= sov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *CallRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Url)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *CallResponse) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Json)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
l = len(m.Error)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
n += len(m.unknownFields)
return n
}
func sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func soz(x uint64) (n int) {
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *CallRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CallRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CallRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Url = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *CallResponse) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CallResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CallResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Json", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Json = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Error = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skip(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLength
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLength
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
)
+88
View File
@@ -0,0 +1,88 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// SubsonicAPICallRequest is the request type for SubsonicAPI.Call.
type SubsonicAPICallRequest struct {
Uri string `json:"uri"`
}
// SubsonicAPICallResponse is the response type for SubsonicAPI.Call.
type SubsonicAPICallResponse struct {
ResponseJSON string `json:"responseJson,omitempty"`
Error string `json:"error,omitempty"`
}
// RegisterSubsonicAPIHostFunctions registers SubsonicAPI service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterSubsonicAPIHostFunctions(service SubsonicAPIService) []extism.HostFunction {
return []extism.HostFunction{
newSubsonicAPICallHostFunction(service),
}
}
func newSubsonicAPICallHostFunction(service SubsonicAPIService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"subsonicapi_call",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
subsonicapiWriteError(p, stack, err)
return
}
var req SubsonicAPICallRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
subsonicapiWriteError(p, stack, err)
return
}
// Call the service method
responsejson, svcErr := service.Call(ctx, req.Uri)
if svcErr != nil {
subsonicapiWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := SubsonicAPICallResponse{
ResponseJSON: responsejson,
}
subsonicapiWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// subsonicapiWriteResponse writes a JSON response to plugin memory.
func subsonicapiWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
subsonicapiWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// subsonicapiWriteError writes an error response to plugin memory.
func subsonicapiWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+35
View File
@@ -0,0 +1,35 @@
package host
import "context"
// User represents a Navidrome user with minimal information exposed to plugins.
// Sensitive fields like password, email, and internal IDs are intentionally excluded.
type User struct {
UserName string `json:"userName"`
Name string `json:"name"`
IsAdmin bool `json:"isAdmin"`
}
// UsersService provides access to user information for plugins.
//
// This service allows plugins to query information about users that the plugin
// has been granted access to. Access is controlled by the administrator who
// configures which users each plugin can see.
//
//nd:hostservice name=Users permission=users
type UsersService interface {
// GetUsers returns all users the plugin has been granted access to.
// Only minimal user information (userName, name, isAdmin) is returned.
// Sensitive fields like password and email are never exposed.
//
// Returns a slice of users the plugin can access, or an empty slice if none configured.
//nd:hostfunc
GetUsers(ctx context.Context) ([]User, error)
// GetAdmins returns only admin users the plugin has been granted access to.
// This is a convenience method that filters GetUsers results to include only admins.
//
// Returns a slice of admin users the plugin can access, or an empty slice if none.
//nd:hostfunc
GetAdmins(ctx context.Context) ([]User, error)
}
+102
View File
@@ -0,0 +1,102 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// UsersGetUsersResponse is the response type for Users.GetUsers.
type UsersGetUsersResponse struct {
Result []User `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
// UsersGetAdminsResponse is the response type for Users.GetAdmins.
type UsersGetAdminsResponse struct {
Result []User `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
// RegisterUsersHostFunctions registers Users service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterUsersHostFunctions(service UsersService) []extism.HostFunction {
return []extism.HostFunction{
newUsersGetUsersHostFunction(service),
newUsersGetAdminsHostFunction(service),
}
}
func newUsersGetUsersHostFunction(service UsersService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"users_getusers",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Call the service method
result, svcErr := service.GetUsers(ctx)
if svcErr != nil {
usersWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := UsersGetUsersResponse{
Result: result,
}
usersWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newUsersGetAdminsHostFunction(service UsersService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"users_getadmins",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Call the service method
result, svcErr := service.GetAdmins(ctx)
if svcErr != nil {
usersWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := UsersGetAdminsResponse{
Result: result,
}
usersWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// usersWriteResponse writes a JSON response to plugin memory.
func usersWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
usersWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// usersWriteError writes an error response to plugin memory.
func usersWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}
+59
View File
@@ -0,0 +1,59 @@
package host
import "context"
// WebSocketService provides WebSocket communication capabilities for plugins.
//
// This service allows plugins to establish WebSocket connections to external services,
// send and receive messages, and manage connection lifecycle. Plugins using this service
// must implement the WebSocketCallback capability to receive incoming messages and
// connection state changes.
//
//nd:hostservice name=WebSocket permission=websocket
type WebSocketService interface {
// Connect establishes a WebSocket connection to the specified URL.
//
// Plugins that use this function must also implement the WebSocketCallback capability
// to receive incoming messages and connection events.
//
// Parameters:
// - url: The WebSocket URL to connect to (ws:// or wss://)
// - headers: Optional HTTP headers to include in the handshake request
// - connectionID: Optional unique identifier for the connection. If empty, one will be generated
//
// Returns the connection ID that can be used to send messages or close the connection,
// or an error if the connection fails.
//nd:hostfunc
Connect(ctx context.Context, url string, headers map[string]string, connectionID string) (newConnectionID string, err error)
// SendText sends a text message over an established WebSocket connection.
//
// Parameters:
// - connectionID: The connection identifier returned by Connect
// - message: The text message to send
//
// Returns an error if the connection is not found or if sending fails.
//nd:hostfunc
SendText(ctx context.Context, connectionID, message string) error
// SendBinary sends binary data over an established WebSocket connection.
//
// Parameters:
// - connectionID: The connection identifier returned by Connect
// - data: The binary data to send
//
// Returns an error if the connection is not found or if sending fails.
//nd:hostfunc
SendBinary(ctx context.Context, connectionID string, data []byte) error
// CloseConnection gracefully closes a WebSocket connection.
//
// Parameters:
// - connectionID: The connection identifier returned by Connect
// - code: WebSocket close status code (e.g., 1000 for normal closure)
// - reason: Optional human-readable reason for closing
//
// Returns an error if the connection is not found or if closing fails.
//nd:hostfunc
CloseConnection(ctx context.Context, connectionID string, code int32, reason string) error
}
-240
View File
@@ -1,240 +0,0 @@
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/websocket/websocket.proto
package websocket
import (
context "context"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ConnectRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ConnectionId string `protobuf:"bytes,3,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
}
func (x *ConnectRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *ConnectRequest) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *ConnectRequest) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (x *ConnectRequest) GetConnectionId() string {
if x != nil {
return x.ConnectionId
}
return ""
}
type ConnectResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *ConnectResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *ConnectResponse) GetConnectionId() string {
if x != nil {
return x.ConnectionId
}
return ""
}
func (x *ConnectResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type SendTextRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *SendTextRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SendTextRequest) GetConnectionId() string {
if x != nil {
return x.ConnectionId
}
return ""
}
func (x *SendTextRequest) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type SendTextResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *SendTextResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SendTextResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type SendBinaryRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *SendBinaryRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SendBinaryRequest) GetConnectionId() string {
if x != nil {
return x.ConnectionId
}
return ""
}
func (x *SendBinaryRequest) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type SendBinaryResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *SendBinaryResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *SendBinaryResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type CloseRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
Code int32 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"`
Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"`
}
func (x *CloseRequest) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *CloseRequest) GetConnectionId() string {
if x != nil {
return x.ConnectionId
}
return ""
}
func (x *CloseRequest) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *CloseRequest) GetReason() string {
if x != nil {
return x.Reason
}
return ""
}
type CloseResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *CloseResponse) ProtoReflect() protoreflect.Message {
panic(`not implemented`)
}
func (x *CloseResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
// go:plugin type=host version=1
type WebSocketService interface {
// Connect to a WebSocket endpoint
Connect(context.Context, *ConnectRequest) (*ConnectResponse, error)
// Send a text message
SendText(context.Context, *SendTextRequest) (*SendTextResponse, error)
// Send binary data
SendBinary(context.Context, *SendBinaryRequest) (*SendBinaryResponse, error)
// Close a connection
Close(context.Context, *CloseRequest) (*CloseResponse, error)
}
-57
View File
@@ -1,57 +0,0 @@
syntax = "proto3";
package websocket;
option go_package = "github.com/navidrome/navidrome/plugins/host/websocket";
// go:plugin type=host version=1
service WebSocketService {
// Connect to a WebSocket endpoint
rpc Connect(ConnectRequest) returns (ConnectResponse);
// Send a text message
rpc SendText(SendTextRequest) returns (SendTextResponse);
// Send binary data
rpc SendBinary(SendBinaryRequest) returns (SendBinaryResponse);
// Close a connection
rpc Close(CloseRequest) returns (CloseResponse);
}
message ConnectRequest {
string url = 1;
map<string, string> headers = 2;
string connection_id = 3;
}
message ConnectResponse {
string connection_id = 1;
string error = 2;
}
message SendTextRequest {
string connection_id = 1;
string message = 2;
}
message SendTextResponse {
string error = 1;
}
message SendBinaryRequest {
string connection_id = 1;
bytes data = 2;
}
message SendBinaryResponse {
string error = 1;
}
message CloseRequest {
string connection_id = 1;
int32 code = 2;
string reason = 3;
}
message CloseResponse {
string error = 1;
}
-170
View File
@@ -1,170 +0,0 @@
//go:build !wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/websocket/websocket.proto
package websocket
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
wazero "github.com/tetratelabs/wazero"
api "github.com/tetratelabs/wazero/api"
)
const (
i32 = api.ValueTypeI32
i64 = api.ValueTypeI64
)
type _webSocketService struct {
WebSocketService
}
// Instantiate a Go-defined module named "env" that exports host functions.
func Instantiate(ctx context.Context, r wazero.Runtime, hostFunctions WebSocketService) error {
envBuilder := r.NewHostModuleBuilder("env")
h := _webSocketService{hostFunctions}
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Connect), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("connect")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._SendText), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("send_text")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._SendBinary), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("send_binary")
envBuilder.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(h._Close), []api.ValueType{i32, i32}, []api.ValueType{i64}).
WithParameterNames("offset", "size").
Export("close")
_, err := envBuilder.Instantiate(ctx)
return err
}
// Connect to a WebSocket endpoint
func (h _webSocketService) _Connect(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(ConnectRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Connect(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Send a text message
func (h _webSocketService) _SendText(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(SendTextRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.SendText(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Send binary data
func (h _webSocketService) _SendBinary(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(SendBinaryRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.SendBinary(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
// Close a connection
func (h _webSocketService) _Close(ctx context.Context, m api.Module, stack []uint64) {
offset, size := uint32(stack[0]), uint32(stack[1])
buf, err := wasm.ReadMemory(m.Memory(), offset, size)
if err != nil {
panic(err)
}
request := new(CloseRequest)
err = request.UnmarshalVT(buf)
if err != nil {
panic(err)
}
resp, err := h.Close(ctx, request)
if err != nil {
panic(err)
}
buf, err = resp.MarshalVT()
if err != nil {
panic(err)
}
ptr, err := wasm.WriteMemory(ctx, m, buf)
if err != nil {
panic(err)
}
ptrLen := (ptr << uint64(32)) | uint64(len(buf))
stack[0] = ptrLen
}
@@ -1,113 +0,0 @@
//go:build wasip1
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
// versions:
// protoc-gen-go-plugin v0.1.0
// protoc v5.29.3
// source: host/websocket/websocket.proto
package websocket
import (
context "context"
wasm "github.com/knqyf263/go-plugin/wasm"
_ "unsafe"
)
type webSocketService struct{}
func NewWebSocketService() WebSocketService {
return webSocketService{}
}
//go:wasmimport env connect
func _connect(ptr uint32, size uint32) uint64
func (h webSocketService) Connect(ctx context.Context, request *ConnectRequest) (*ConnectResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _connect(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(ConnectResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env send_text
func _send_text(ptr uint32, size uint32) uint64
func (h webSocketService) SendText(ctx context.Context, request *SendTextRequest) (*SendTextResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _send_text(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(SendTextResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env send_binary
func _send_binary(ptr uint32, size uint32) uint64
func (h webSocketService) SendBinary(ctx context.Context, request *SendBinaryRequest) (*SendBinaryResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _send_binary(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(SendBinaryResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
//go:wasmimport env close
func _close(ptr uint32, size uint32) uint64
func (h webSocketService) Close(ctx context.Context, request *CloseRequest) (*CloseResponse, error) {
buf, err := request.MarshalVT()
if err != nil {
return nil, err
}
ptr, size := wasm.ByteToPtr(buf)
ptrSize := _close(ptr, size)
wasm.Free(ptr)
ptr = uint32(ptrSize >> 32)
size = uint32(ptrSize)
buf = wasm.PtrToByte(ptr, size)
response := new(CloseResponse)
if err = response.UnmarshalVT(buf); err != nil {
return nil, err
}
return response, nil
}
@@ -1,7 +0,0 @@
//go:build !wasip1
package websocket
func NewWebSocketService() WebSocketService {
panic("not implemented")
}
File diff suppressed because it is too large Load Diff
+220
View File
@@ -0,0 +1,220 @@
// Code generated by ndpgen. DO NOT EDIT.
package host
import (
"context"
"encoding/json"
extism "github.com/extism/go-sdk"
)
// WebSocketConnectRequest is the request type for WebSocket.Connect.
type WebSocketConnectRequest struct {
Url string `json:"url"`
Headers map[string]string `json:"headers"`
ConnectionID string `json:"connectionId"`
}
// WebSocketConnectResponse is the response type for WebSocket.Connect.
type WebSocketConnectResponse struct {
NewConnectionID string `json:"newConnectionId,omitempty"`
Error string `json:"error,omitempty"`
}
// WebSocketSendTextRequest is the request type for WebSocket.SendText.
type WebSocketSendTextRequest struct {
ConnectionID string `json:"connectionId"`
Message string `json:"message"`
}
// WebSocketSendTextResponse is the response type for WebSocket.SendText.
type WebSocketSendTextResponse struct {
Error string `json:"error,omitempty"`
}
// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary.
type WebSocketSendBinaryRequest struct {
ConnectionID string `json:"connectionId"`
Data []byte `json:"data"`
}
// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary.
type WebSocketSendBinaryResponse struct {
Error string `json:"error,omitempty"`
}
// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection.
type WebSocketCloseConnectionRequest struct {
ConnectionID string `json:"connectionId"`
Code int32 `json:"code"`
Reason string `json:"reason"`
}
// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection.
type WebSocketCloseConnectionResponse struct {
Error string `json:"error,omitempty"`
}
// RegisterWebSocketHostFunctions registers WebSocket service host functions.
// The returned host functions should be added to the plugin's configuration.
func RegisterWebSocketHostFunctions(service WebSocketService) []extism.HostFunction {
return []extism.HostFunction{
newWebSocketConnectHostFunction(service),
newWebSocketSendTextHostFunction(service),
newWebSocketSendBinaryHostFunction(service),
newWebSocketCloseConnectionHostFunction(service),
}
}
func newWebSocketConnectHostFunction(service WebSocketService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"websocket_connect",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
websocketWriteError(p, stack, err)
return
}
var req WebSocketConnectRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
websocketWriteError(p, stack, err)
return
}
// Call the service method
newconnectionid, svcErr := service.Connect(ctx, req.Url, req.Headers, req.ConnectionID)
if svcErr != nil {
websocketWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := WebSocketConnectResponse{
NewConnectionID: newconnectionid,
}
websocketWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newWebSocketSendTextHostFunction(service WebSocketService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"websocket_sendtext",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
websocketWriteError(p, stack, err)
return
}
var req WebSocketSendTextRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
websocketWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.SendText(ctx, req.ConnectionID, req.Message); svcErr != nil {
websocketWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := WebSocketSendTextResponse{}
websocketWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newWebSocketSendBinaryHostFunction(service WebSocketService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"websocket_sendbinary",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
websocketWriteError(p, stack, err)
return
}
var req WebSocketSendBinaryRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
websocketWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.SendBinary(ctx, req.ConnectionID, req.Data); svcErr != nil {
websocketWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := WebSocketSendBinaryResponse{}
websocketWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
func newWebSocketCloseConnectionHostFunction(service WebSocketService) extism.HostFunction {
return extism.NewHostFunctionWithStack(
"websocket_closeconnection",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
// Read JSON request from plugin memory
reqBytes, err := p.ReadBytes(stack[0])
if err != nil {
websocketWriteError(p, stack, err)
return
}
var req WebSocketCloseConnectionRequest
if err := json.Unmarshal(reqBytes, &req); err != nil {
websocketWriteError(p, stack, err)
return
}
// Call the service method
if svcErr := service.CloseConnection(ctx, req.ConnectionID, req.Code, req.Reason); svcErr != nil {
websocketWriteError(p, stack, svcErr)
return
}
// Write JSON response to plugin memory
resp := WebSocketCloseConnectionResponse{}
websocketWriteResponse(p, stack, resp)
},
[]extism.ValueType{extism.ValueTypePTR},
[]extism.ValueType{extism.ValueTypePTR},
)
}
// websocketWriteResponse writes a JSON response to plugin memory.
func websocketWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
respBytes, err := json.Marshal(resp)
if err != nil {
websocketWriteError(p, stack, err)
return
}
respPtr, err := p.WriteBytes(respBytes)
if err != nil {
stack[0] = 0
return
}
stack[0] = respPtr
}
// websocketWriteError writes an error response to plugin memory.
func websocketWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
errResp := struct {
Error string `json:"error"`
}{Error: err.Error()}
respBytes, _ := json.Marshal(errResp)
respPtr, _ := p.WriteBytes(respBytes)
stack[0] = respPtr
}