fulltime-plugin-api 0.1.0

Canonical league-data schema, data-provider WIT interface, and plugin manifest format shared by the FullTime plugin host and data-provider plugins.
Documentation
# Authoring a data-provider plugin

A data-provider plugin is a WASM component that implements the `data-provider` interface
defined in [`wit/data-provider.wit`](../wit/data-provider.wit) and ships a manifest
declaring its identity and required network access. This crate (`fulltime-plugin-api`) is
the only dependency required to build one: it provides the canonical schema types, the
generated interface bindings, and the manifest parser.

Neither the host (`Apps/rust`) nor any plugin's implementation lives in this repo — this
repo only defines the contract. The reference implementation is `Plugins/Bundesliga`,
wrapping `Libs/openligadb`.

## The contract

### 1. Canonical schema

Every value a plugin returns is typed against the canonical schema, not a provider-specific
shape:

- `Competition` — a league, cup, or tournament.
- `Team` — a participant, identified consistently across every plugin.
- `Fixture` — a scheduled or completed match. The optional `group` field distinguishes
  group-stage fixtures (e.g. `"Group A"`) from single-table league fixtures (`None`).
- `Standings` — one or more `StandingsGroup`s, each a ranked list of `StandingsRow`. A
  single-table league format is one unnamed group; a group-stage tournament is several
  named groups sharing the same row shape.

See the type definitions in [`wit/data-provider.wit`](../wit/data-provider.wit) (`interface
types`) for the authoritative field list — the Rust types in this crate are generated
directly from it, so the two never drift.

### 2. The `data-provider` interface

Implement the five operations in `interface data-provider`:

| Function | Returns |
|---|---|
| `list-competitions` | `list<competition>` |
| `fetch-fixtures` | `list<fixture>` for a competition (scheduled/live) |
| `fetch-results` | `list<fixture>` for a competition (finished) |
| `fetch-standings` | `standings` for a competition |
| `fetch-metadata` | `competition` |

Every function returns a `result<T, provider-error>`. Use the structured error variants
instead of letting an upstream failure surface as an unhandled trap:

- `network-failure` — the upstream HTTP call failed at the network layer.
- `rate-limited` — the upstream source responded with a rate limit, optionally carrying
  `retry-after-seconds`.
- `schema-mapping-failure` — the upstream response can't be represented in the canonical
  schema.

### 3. The manifest

Every plugin ships a TOML manifest, parsed at load time by [`Manifest::parse`]:

```toml
id = "bundesliga"
version = "0.1.0"
schema_version = "1.0"
interface_version = "1.0"
network_hosts = ["api.openligadb.de"]
```

- `id` — unique among plugins the host loads.
- `version` — the plugin's own release version, unrelated to the contract versions below.
- `schema_version` / `interface_version` — the `major.minor` contract versions this plugin
  was built against (see [`SCHEMA_VERSION`] and [`INTERFACE_VERSION`]).
- `network_hosts` — every hostname the plugin needs to call. The host runtime scopes the
  plugin's HTTP fetch capability to exactly this list; a plugin has no network access to
  anything not declared here. This crate validates the field is present and well-formed,
  not that the hosts are reachable — that enforcement lives in the host runtime.

## Versioning

The schema and the interface version independently, each as a `major.minor` pair, because a
schema field addition and an interface signature change are unrelated concerns. A host
accepts a plugin when, for both versions, the major matches and the host's minor is equal
to or greater than the plugin's declared minor — the host being a superset of what the
plugin expects. See [`Version::accepts`].

When either version changes:

- **Minor bump**: additive only (new optional field, new function that doesn't change
  existing signatures). Existing plugins keep working against a newer host.
- **Major bump**: breaking (removed/renamed field, changed function signature). Plugins
  must be rebuilt and republish their manifest's target version.

## Getting started

1. Add this crate as a dependency:
   ```toml
   [dependencies]
   fulltime-plugin-api = "0.1"
   ```
2. Implement the `data-provider` world's exported interface against your upstream data
   source, mapping its response shape into the canonical schema types.
3. Write your `manifest.toml` declaring the network hosts you call.
4. Build to a WASM component target and load it against the host runtime in `Apps/rust`.

[`Manifest::parse`]: https://docs.rs/fulltime-plugin-api/latest/fulltime_plugin_api/struct.Manifest.html#method.parse
[`SCHEMA_VERSION`]: https://docs.rs/fulltime-plugin-api/latest/fulltime_plugin_api/constant.SCHEMA_VERSION.html
[`INTERFACE_VERSION`]: https://docs.rs/fulltime-plugin-api/latest/fulltime_plugin_api/constant.INTERFACE_VERSION.html
[`Version::accepts`]: https://docs.rs/fulltime-plugin-api/latest/fulltime_plugin_api/struct.Version.html#method.accepts