199 lines
7.4 KiB
Markdown
199 lines
7.4 KiB
Markdown
|
|
# ndpgen
|
||
|
|
|
||
|
|
Navidrome Plugin Development Kit (PDK) code generator. It reads Go interface definitions with special annotations and generates client wrappers for WASM plugins.
|
||
|
|
|
||
|
|
This tool is the unified code generator that handle both host function wrappers and capability wrappers.
|
||
|
|
|
||
|
|
## Usage
|
||
|
|
|
||
|
|
```bash
|
||
|
|
ndpgen -input <dir> -output <dir> [-package <name>] [-v] [-dry-run] [-host-only] [-go] [-python] [-rust]
|
||
|
|
```
|
||
|
|
|
||
|
|
### Flags
|
||
|
|
|
||
|
|
| Flag | Description | Default |
|
||
|
|
|--------------|----------------------------------------------------------------|----------------------|
|
||
|
|
| `-input` | Directory containing Go source files with annotated interfaces | Required |
|
||
|
|
| `-output` | Directory where generated files will be written | Same as input |
|
||
|
|
| `-package` | Package name for generated files | Inferred from output |
|
||
|
|
| `-v` | Verbose output | `false` |
|
||
|
|
| `-dry-run` | Parse and validate without writing files | `false` |
|
||
|
|
| `-host-only` | Generate only host function wrappers (capability support TBD) | `true` |
|
||
|
|
| `-go` | Generate Go client wrappers | `true`* |
|
||
|
|
| `-python` | Generate Python client wrappers | `false` |
|
||
|
|
| `-rust` | Generate Rust client wrappers | `false` |
|
||
|
|
|
||
|
|
\* `-go` is enabled by default when neither `-python` nor `-rust` is specified. Use combinations like `-go -python -rust` to generate multiple languages.
|
||
|
|
|
||
|
|
### Example
|
||
|
|
|
||
|
|
```bash
|
||
|
|
go run ./plugins/cmd/ndpgen \
|
||
|
|
-input ./plugins/host \
|
||
|
|
-output ./plugins/pdk
|
||
|
|
```
|
||
|
|
|
||
|
|
## Annotations
|
||
|
|
|
||
|
|
### `//nd:hostservice`
|
||
|
|
|
||
|
|
Marks an interface as a host service that will have wrappers generated.
|
||
|
|
|
||
|
|
```go
|
||
|
|
//nd:hostservice name=<ServiceName> permission=<permission>
|
||
|
|
type MyService interface { ... }
|
||
|
|
```
|
||
|
|
|
||
|
|
| Parameter | Description | Required |
|
||
|
|
|--------------|-----------------------------------------------------------------|----------|
|
||
|
|
| `name` | Service name used in generated type names and function prefixes | Yes |
|
||
|
|
| `permission` | Permission required by plugins to use this service | Yes |
|
||
|
|
|
||
|
|
### `//nd:hostfunc`
|
||
|
|
|
||
|
|
Marks a method within a host service interface for export to plugins.
|
||
|
|
|
||
|
|
```go
|
||
|
|
//nd:hostfunc [name=<export_name>]
|
||
|
|
MethodName(ctx context.Context, ...) (result Type, err error)
|
||
|
|
```
|
||
|
|
|
||
|
|
| Parameter | Description | Required |
|
||
|
|
|-----------|-------------------------------------------------------------------------|----------|
|
||
|
|
| `name` | Custom export name (default: `<servicename>_<methodname>` in lowercase) | No |
|
||
|
|
|
||
|
|
## Input Format
|
||
|
|
|
||
|
|
Host service interfaces must follow these conventions:
|
||
|
|
|
||
|
|
1. **First parameter must be `context.Context`** - Required for all methods
|
||
|
|
2. **Last return value should be `error`** - For proper error handling
|
||
|
|
3. **Annotations must be on consecutive lines** - No blank comment lines between doc and annotation
|
||
|
|
|
||
|
|
### Example Interface
|
||
|
|
|
||
|
|
```go
|
||
|
|
package host
|
||
|
|
|
||
|
|
import "context"
|
||
|
|
|
||
|
|
// SubsonicAPIService provides access to Navidrome's Subsonic API.
|
||
|
|
// This documentation becomes part of the generated code.
|
||
|
|
//nd:hostservice name=SubsonicAPI permission=subsonicapi
|
||
|
|
type SubsonicAPIService interface {
|
||
|
|
// Call executes a Subsonic API request and returns the response.
|
||
|
|
//nd:hostfunc
|
||
|
|
Call(ctx context.Context, uri string) (response string, err error)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Generated Output
|
||
|
|
|
||
|
|
### Go Client Library (Go/TinyGo WASM)
|
||
|
|
|
||
|
|
Generated files are named `nd_host_<servicename>.go` (lowercase) and placed in `$output/go/host/`. The `$output/go/` directory becomes a complete Go module (`github.com/navidrome/navidrome/plugins/pdk/go`) with package name `host`, intended for import by Navidrome plugins built with TinyGo.
|
||
|
|
|
||
|
|
The generator creates:
|
||
|
|
- `nd_host_<servicename>.go` - Client wrapper code (WASM build)
|
||
|
|
- `nd_host_<servicename>_stub.go` - Mock implementations for non-WASM platforms (testing)
|
||
|
|
- `doc.go` - Package documentation listing all available services
|
||
|
|
- `go.mod` - Go module file with required dependencies
|
||
|
|
|
||
|
|
Each service file includes:
|
||
|
|
|
||
|
|
- `// Code generated by ndpgen. DO NOT EDIT.` header
|
||
|
|
- Required imports (`encoding/json`, `errors`, `github.com/extism/go-pdk`)
|
||
|
|
- `//go:wasmimport` declarations for each host function
|
||
|
|
- Response struct types and any struct definitions from the service
|
||
|
|
- Wrapper functions that handle memory allocation and JSON parsing
|
||
|
|
|
||
|
|
### Testing Plugins with Mocks
|
||
|
|
|
||
|
|
The stub files (`*_stub.go`) contain [testify/mock](https://github.com/stretchr/testify) implementations that allow plugin authors to unit test their code on non-WASM platforms.
|
||
|
|
|
||
|
|
Each host service has:
|
||
|
|
- A private mock struct embedding `mock.Mock`
|
||
|
|
- An exported auto-instantiated mock instance (e.g., `host.CacheMock`, `host.ArtworkMock`)
|
||
|
|
- Wrapper functions that delegate to the mock
|
||
|
|
|
||
|
|
**Example: Testing a plugin that uses the Cache service**
|
||
|
|
|
||
|
|
```go
|
||
|
|
package myplugin
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestMyPluginFunction(t *testing.T) {
|
||
|
|
// Set expectations on the mock
|
||
|
|
host.CacheMock.On("GetString", "my-key").Return("cached-value", true, nil)
|
||
|
|
host.CacheMock.On("SetString", "new-key", "new-value", int64(3600)).Return(nil)
|
||
|
|
|
||
|
|
// Call your plugin code that uses host.CacheGetString and host.CacheSetString
|
||
|
|
result := myPluginFunction()
|
||
|
|
|
||
|
|
// Assert the result
|
||
|
|
if result != "expected" {
|
||
|
|
t.Errorf("unexpected result: %s", result)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify all expected calls were made
|
||
|
|
host.CacheMock.AssertExpectations(t)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Resetting mocks between tests:**
|
||
|
|
|
||
|
|
If you need to reset mock state between tests, testify's mock doesn't have a built-in reset. Either use separate test functions (testify automatically resets between test runs), or create a helper to set up fresh expectations.
|
||
|
|
|
||
|
|
### Python Client Library
|
||
|
|
|
||
|
|
When using `-python`, Python client files are generated in a `python/` subdirectory.
|
||
|
|
|
||
|
|
### Rust Client Library
|
||
|
|
|
||
|
|
When using `-rust`, Rust client files are generated in a `rust/` subdirectory.
|
||
|
|
|
||
|
|
## Supported Types
|
||
|
|
|
||
|
|
ndpgen supports these Go types in method signatures:
|
||
|
|
|
||
|
|
| Type | JSON Representation |
|
||
|
|
|-------------------------------|------------------------------------------|
|
||
|
|
| `string`, `int`, `bool`, etc. | Native JSON types |
|
||
|
|
| `[]T` (slices) | JSON arrays |
|
||
|
|
| `map[K]V` (maps) | JSON objects |
|
||
|
|
| `*T` (pointers) | Nullable fields |
|
||
|
|
| `interface{}` / `any` | Converts to `any` |
|
||
|
|
| Custom structs | JSON objects (must be JSON-serializable) |
|
||
|
|
|
||
|
|
### Multiple Return Values
|
||
|
|
|
||
|
|
Methods can return multiple values (plus error):
|
||
|
|
|
||
|
|
```go
|
||
|
|
//nd:hostfunc
|
||
|
|
Search(ctx context.Context, query string) (results []string, total int, hasMore bool, err error)
|
||
|
|
```
|
||
|
|
|
||
|
|
Generates:
|
||
|
|
|
||
|
|
```go
|
||
|
|
type ServiceSearchResponse struct {
|
||
|
|
Results []string `json:"results,omitempty"`
|
||
|
|
Total int `json:"total,omitempty"`
|
||
|
|
HasMore bool `json:"hasMore,omitempty"`
|
||
|
|
Error string `json:"error,omitempty"`
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Running Tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
go test ./plugins/cmd/ndpgen/...
|
||
|
|
```
|