provider_registry.go

  1package fantasy
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"sync"
  7)
  8
  9// providerDataJSON is the serialized wrapper used by the registry.
 10type providerDataJSON struct {
 11	Type string          `json:"type"`
 12	Data json.RawMessage `json:"data"`
 13}
 14
 15// UnmarshalFunc converts raw JSON into a ProviderOptionsData implementation.
 16type UnmarshalFunc func([]byte) (ProviderOptionsData, error)
 17
 18// providerRegistry uses sync.Map for lock-free reads after initialization.
 19// All registrations happen in init() functions before concurrent access.
 20var providerRegistry sync.Map
 21
 22// RegisterProviderType registers a provider type ID with its unmarshal function.
 23// Type IDs must be globally unique (e.g. "openai.options").
 24// This should only be called during package initialization (init functions).
 25func RegisterProviderType(typeID string, unmarshalFn UnmarshalFunc) {
 26	providerRegistry.Store(typeID, unmarshalFn)
 27}
 28
 29// unmarshalProviderData routes a typed payload to the correct constructor.
 30func unmarshalProviderData(data []byte) (ProviderOptionsData, error) {
 31	var pj providerDataJSON
 32	if err := json.Unmarshal(data, &pj); err != nil {
 33		return nil, err
 34	}
 35
 36	val, exists := providerRegistry.Load(pj.Type)
 37	if !exists {
 38		return nil, fmt.Errorf("unknown provider data type: %s", pj.Type)
 39	}
 40
 41	unmarshalFn := val.(UnmarshalFunc)
 42	return unmarshalFn(pj.Data)
 43}
 44
 45// unmarshalProviderDataMap is a helper for unmarshaling maps of provider data.
 46func unmarshalProviderDataMap(data map[string]json.RawMessage) (map[string]ProviderOptionsData, error) {
 47	result := make(map[string]ProviderOptionsData)
 48	for provider, rawData := range data {
 49		providerData, err := unmarshalProviderData(rawData)
 50		if err != nil {
 51			return nil, fmt.Errorf("failed to unmarshal provider data for %s: %w", provider, err)
 52		}
 53		result[provider] = providerData
 54	}
 55	return result, nil
 56}
 57
 58// UnmarshalProviderOptions unmarshals a map of provider options by type.
 59func UnmarshalProviderOptions(data map[string]json.RawMessage) (ProviderOptions, error) {
 60	return unmarshalProviderDataMap(data)
 61}
 62
 63// UnmarshalProviderMetadata unmarshals a map of provider metadata by type.
 64func UnmarshalProviderMetadata(data map[string]json.RawMessage) (ProviderMetadata, error) {
 65	return unmarshalProviderDataMap(data)
 66}
 67
 68// MarshalProviderType marshals provider data with a type wrapper using generics.
 69// To avoid infinite recursion, use the "type plain T" pattern before calling this.
 70//
 71// Usage in provider types:
 72//
 73//	func (o ProviderOptions) MarshalJSON() ([]byte, error) {
 74//	    type plain ProviderOptions
 75//	    return fantasy.MarshalProviderType(TypeProviderOptions, plain(o))
 76//	}
 77func MarshalProviderType[T any](typeID string, data T) ([]byte, error) {
 78	rawData, err := json.Marshal(data)
 79	if err != nil {
 80		return nil, err
 81	}
 82
 83	return json.Marshal(providerDataJSON{
 84		Type: typeID,
 85		Data: json.RawMessage(rawData),
 86	})
 87}
 88
 89// UnmarshalProviderType unmarshals provider data without type wrapper using generics.
 90// To avoid infinite recursion, unmarshal to a plain type first.
 91// Note: This receives the inner 'data' field after type routing by the registry.
 92//
 93// Usage in provider types:
 94//
 95//	func (o *ProviderOptions) UnmarshalJSON(data []byte) error {
 96//	    type plain ProviderOptions
 97//	    var p plain
 98//	    if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
 99//	        return err
100//	    }
101//	    *o = ProviderOptions(p)
102//	    return nil
103//	}
104func UnmarshalProviderType[T any](data []byte, target *T) error {
105	return json.Unmarshal(data, target)
106}