Files

250 lines
8.8 KiB
Go
Raw Permalink Normal View History

package requests
import (
"context"
"fmt"
"strconv"
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
"google.golang.org/protobuf/types/known/structpb"
)
// RequestRouterProvider fulfills a whole request and checks target status via a
// request_router.v1 plugin. The host owns governance (which qualities) and the
// target records; the provider is the plugin boundary.
type RequestRouterProvider interface {
Fulfill(ctx context.Context, installationID int, capabilityID string, req Request, qualities []Quality, conns []ResolvedRouterConnection) ([]RouterTarget, string, error)
CheckStatus(ctx context.Context, installationID int, capabilityID string, req Request, targets []RouterTargetRef, conns []ResolvedRouterConnection) ([]RouterTargetStatus, error)
ListConfigOptions(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (map[string][]RouterOption, error)
TestConnection(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (bool, string, error)
Validate(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection, siblings []ResolvedRouterConnection) (fieldErrors map[string]string, formError string, err error)
}
// ResolvedRouterConnection is a connection with plaintext credentials + parsed config.
type ResolvedRouterConnection struct {
ID string
BaseURL string
APIKey string
Config map[string]any
}
type RouterTarget struct {
Quality Quality
ConnectionID string
ExternalID string
ExternalStatus string
Status Status
Message string
}
type RouterTargetRef struct {
Quality Quality
ConnectionID string
ExternalID string
}
type RouterTargetStatus struct {
Quality Quality
ConnectionID string
Status Status
ExternalStatus string
Message string
}
type RouterOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
// RouterClientResolver yields a per-(installation, capability) router client.
type RouterClientResolver interface {
RequestRouterClient(ctx context.Context, installationID int, capabilityID string) (RouterClient, error)
}
// RouterClient mirrors *pluginhost.RequestRouterClient (exported so the api
// package can adapt the concrete type across packages without an import cycle).
type RouterClient interface {
Fulfill(ctx context.Context, req *pluginv1.FulfillRequest) (*pluginv1.FulfillResponse, error)
CheckStatus(ctx context.Context, req *pluginv1.CheckStatusRequest) (*pluginv1.CheckStatusResponse, error)
ListConfigOptions(ctx context.Context, req *pluginv1.ListConfigOptionsRequest) (*pluginv1.ListConfigOptionsResponse, error)
TestConnection(ctx context.Context, req *pluginv1.TestConnectionRequest) (*pluginv1.TestConnectionResponse, error)
Validate(ctx context.Context, req *pluginv1.ValidateRequest) (*pluginv1.ValidateResponse, error)
}
type pluginRouterProvider struct{ resolver RouterClientResolver }
func NewPluginRouterProvider(r RouterClientResolver) RequestRouterProvider {
return &pluginRouterProvider{resolver: r}
}
func routerDescriptor(req Request) *pluginv1.RequestDescriptor {
ids := map[string]string{}
if req.TMDBID != 0 {
ids["tmdb"] = strconv.Itoa(req.TMDBID)
}
if req.TVDBID != nil {
ids["tvdb"] = strconv.Itoa(*req.TVDBID)
}
if req.IMDbID != "" {
ids["imdb"] = req.IMDbID
}
year := 0
if req.Year != nil {
year = *req.Year
}
return &pluginv1.RequestDescriptor{
MediaType: string(req.MediaType),
Title: req.Title,
Year: int32(year),
ExternalIds: ids,
IsAnime: req.IsAnime,
RequesterUserId: int64(req.RequestedByUserID),
RequesterProfileId: req.RequestedByProfileID,
RequesterEmail: req.RequesterEmail,
RequesterUsername: req.RequesterUsername,
}
}
func routerProtoConn(c ResolvedRouterConnection) (*pluginv1.RouterConnection, error) {
cfg, err := structpb.NewStruct(c.Config)
if err != nil {
return nil, fmt.Errorf("router: encode connection %s config: %w", c.ID, err)
}
return &pluginv1.RouterConnection{Id: c.ID, BaseUrl: c.BaseURL, ApiKey: c.APIKey, Config: cfg}, nil
}
func routerProtoConns(conns []ResolvedRouterConnection) ([]*pluginv1.RouterConnection, error) {
out := make([]*pluginv1.RouterConnection, 0, len(conns))
for _, c := range conns {
pc, err := routerProtoConn(c)
if err != nil {
return nil, err
}
out = append(out, pc)
}
return out, nil
}
func (p *pluginRouterProvider) Fulfill(ctx context.Context, installationID int, capabilityID string, req Request, qualities []Quality, conns []ResolvedRouterConnection) ([]RouterTarget, string, error) {
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
if err != nil {
return nil, "", err
}
qs := make([]*pluginv1.RequestedQuality, 0, len(qualities))
for _, q := range qualities {
qs = append(qs, &pluginv1.RequestedQuality{Id: string(q), Is4K: q == Quality2160p})
}
pconns, err := routerProtoConns(conns)
if err != nil {
return nil, "", err
}
resp, err := client.Fulfill(ctx, &pluginv1.FulfillRequest{
CapabilityId: capabilityID, Request: routerDescriptor(req), Qualities: qs, Connections: pconns,
})
if err != nil {
return nil, "", err
}
targets := make([]RouterTarget, 0, len(resp.GetTargets()))
for _, t := range resp.GetTargets() {
targets = append(targets, RouterTarget{
Quality: Quality(t.GetQuality()), ConnectionID: t.GetConnectionId(),
ExternalID: t.GetExternalId(), ExternalStatus: t.GetExternalStatus(),
Status: Status(t.GetStatus()), Message: t.GetMessage(),
})
}
return targets, resp.GetMessage(), nil
}
func (p *pluginRouterProvider) CheckStatus(ctx context.Context, installationID int, capabilityID string, req Request, targets []RouterTargetRef, conns []ResolvedRouterConnection) ([]RouterTargetStatus, error) {
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
if err != nil {
return nil, err
}
refs := make([]*pluginv1.TargetRef, 0, len(targets))
for _, t := range targets {
refs = append(refs, &pluginv1.TargetRef{Quality: string(t.Quality), ConnectionId: t.ConnectionID, ExternalId: t.ExternalID})
}
pconns, err := routerProtoConns(conns)
if err != nil {
return nil, err
}
resp, err := client.CheckStatus(ctx, &pluginv1.CheckStatusRequest{
CapabilityId: capabilityID, Request: routerDescriptor(req), Targets: refs, Connections: pconns,
})
if err != nil {
return nil, err
}
out := make([]RouterTargetStatus, 0, len(resp.GetStatuses()))
for _, st := range resp.GetStatuses() {
out = append(out, RouterTargetStatus{
Quality: Quality(st.GetQuality()), ConnectionID: st.GetConnectionId(),
Status: Status(st.GetStatus()), ExternalStatus: st.GetExternalStatus(), Message: st.GetMessage(),
})
}
return out, nil
}
func (p *pluginRouterProvider) ListConfigOptions(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (map[string][]RouterOption, error) {
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
if err != nil {
return nil, err
}
pconn, err := routerProtoConn(conn)
if err != nil {
return nil, err
}
resp, err := client.ListConfigOptions(ctx, &pluginv1.ListConfigOptionsRequest{
CapabilityId: capabilityID, Connection: pconn,
})
if err != nil {
return nil, err
}
out := map[string][]RouterOption{}
for field, list := range resp.GetOptionsByField() {
opts := make([]RouterOption, 0, len(list.GetOptions()))
for _, o := range list.GetOptions() {
opts = append(opts, RouterOption{Value: o.GetValue(), Label: o.GetLabel()})
}
out[field] = opts
}
return out, nil
}
func (p *pluginRouterProvider) TestConnection(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (bool, string, error) {
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
if err != nil {
return false, "", err
}
pconn, err := routerProtoConn(conn)
if err != nil {
return false, "", err
}
resp, err := client.TestConnection(ctx, &pluginv1.TestConnectionRequest{
CapabilityId: capabilityID, Connection: pconn,
})
if err != nil {
return false, "", err
}
return resp.GetOk(), resp.GetMessage(), nil
}
func (p *pluginRouterProvider) Validate(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection, siblings []ResolvedRouterConnection) (map[string]string, string, error) {
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
if err != nil {
return nil, "", err
}
pc, err := routerProtoConn(conn)
if err != nil {
return nil, "", err
}
sibs, err := routerProtoConns(siblings)
if err != nil {
return nil, "", err
}
resp, err := client.Validate(ctx, &pluginv1.ValidateRequest{CapabilityId: capabilityID, Connection: pc, Siblings: sibs})
if err != nil {
return nil, "", err
}
return resp.GetFieldErrors(), resp.GetFormError(), nil
}