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}