Skip to content

Commit b845dea

Browse files
committed
chore: simplify and cleanup
1 parent 9841151 commit b845dea

File tree

10 files changed

+536
-334
lines changed

10 files changed

+536
-334
lines changed

content.go

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,46 @@ import "encoding/json"
77
// encoding/json.Unmarshaler interfaces to ensure proper JSON serialization
88
// with the provider registry system.
99
//
10-
// Required implementation pattern:
10+
// Recommended implementation pattern using generic helpers:
1111
//
12-
// type MyProviderOptions struct {
13-
// Field string `json:"field"`
14-
// }
12+
// // Define type constants at the top of your file
13+
// const TypeMyProviderOptions = "myprovider.options"
1514
//
16-
// // Implement ProviderOptionsData
17-
// func (*MyProviderOptions) Options() {}
18-
//
19-
// // Implement json.Marshaler - use fantasy.MarshalProviderData
20-
// func (m MyProviderOptions) MarshalJSON() ([]byte, error) {
21-
// ...
22-
// }
23-
//
24-
// // Implement json.Unmarshaler - use fantasy.UnmarshalProviderData
25-
// func (m *MyProviderOptions) UnmarshalJSON(data []byte) error {
26-
// ...
27-
// }
28-
//
29-
// Additionally, register the type in init():
15+
// type MyProviderOptions struct {
16+
// Field string `json:"field"`
17+
// }
3018
//
19+
// // Register the type in init() - place at top of file after constants
3120
// func init() {
32-
// fantasy.RegisterProviderType("provider.type", func(data []byte) (fantasy.ProviderOptionsData, error) {
21+
// fantasy.RegisterProviderType(TypeMyProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
3322
// var opts MyProviderOptions
3423
// if err := json.Unmarshal(data, &opts); err != nil {
3524
// return nil, err
3625
// }
3726
// return &opts, nil
3827
// })
3928
// }
29+
//
30+
// // Implement ProviderOptionsData interface
31+
// func (*MyProviderOptions) Options() {}
32+
//
33+
// // Implement json.Marshaler using the generic helper
34+
// func (m MyProviderOptions) MarshalJSON() ([]byte, error) {
35+
// type plain MyProviderOptions
36+
// return fantasy.MarshalProviderType(TypeMyProviderOptions, plain(m))
37+
// }
38+
//
39+
// // Implement json.Unmarshaler using the generic helper
40+
// // Note: Receives inner data after type routing by the registry.
41+
// func (m *MyProviderOptions) UnmarshalJSON(data []byte) error {
42+
// type plain MyProviderOptions
43+
// var p plain
44+
// if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
45+
// return err
46+
// }
47+
// *m = MyProviderOptions(p)
48+
// return nil
49+
// }
4050
type ProviderOptionsData interface {
4151
// Options is a marker method that identifies types implementing this interface.
4252
Options()

content_json.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,22 +248,27 @@ func (s *SourceContent) UnmarshalJSON(data []byte) error {
248248

249249
// MarshalJSON implements json.Marshaler for ToolCallContent.
250250
func (t ToolCallContent) MarshalJSON() ([]byte, error) {
251+
var validationErrMsg *string
252+
if t.ValidationError != nil {
253+
msg := t.ValidationError.Error()
254+
validationErrMsg = &msg
255+
}
251256
dataBytes, err := json.Marshal(struct {
252257
ToolCallID string `json:"tool_call_id"`
253258
ToolName string `json:"tool_name"`
254259
Input string `json:"input"`
255260
ProviderExecuted bool `json:"provider_executed"`
256261
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
257262
Invalid bool `json:"invalid,omitempty"`
258-
ValidationError error `json:"validation_error,omitempty"`
263+
ValidationError *string `json:"validation_error,omitempty"`
259264
}{
260265
ToolCallID: t.ToolCallID,
261266
ToolName: t.ToolName,
262267
Input: t.Input,
263268
ProviderExecuted: t.ProviderExecuted,
264269
ProviderMetadata: t.ProviderMetadata,
265270
Invalid: t.Invalid,
266-
ValidationError: t.ValidationError,
271+
ValidationError: validationErrMsg,
267272
})
268273
if err != nil {
269274
return nil, err
@@ -289,7 +294,7 @@ func (t *ToolCallContent) UnmarshalJSON(data []byte) error {
289294
ProviderExecuted bool `json:"provider_executed"`
290295
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
291296
Invalid bool `json:"invalid,omitempty"`
292-
ValidationError error `json:"validation_error,omitempty"`
297+
ValidationError *string `json:"validation_error,omitempty"`
293298
}
294299

295300
if err := json.Unmarshal(cj.Data, &aux); err != nil {
@@ -301,7 +306,9 @@ func (t *ToolCallContent) UnmarshalJSON(data []byte) error {
301306
t.Input = aux.Input
302307
t.ProviderExecuted = aux.ProviderExecuted
303308
t.Invalid = aux.Invalid
304-
t.ValidationError = aux.ValidationError
309+
if aux.ValidationError != nil {
310+
t.ValidationError = errors.New(*aux.ValidationError)
311+
}
305312

306313
if len(aux.ProviderMetadata) > 0 {
307314
metadata, err := UnmarshalProviderMetadata(aux.ProviderMetadata)

provider_registry.go

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@ type providerDataJSON struct {
1515
// UnmarshalFunc converts raw JSON into a ProviderOptionsData implementation.
1616
type UnmarshalFunc func([]byte) (ProviderOptionsData, error)
1717

18-
var (
19-
providerRegistry = make(map[string]UnmarshalFunc)
20-
registryMutex sync.RWMutex
21-
)
18+
// providerRegistry uses sync.Map for lock-free reads after initialization.
19+
// All registrations happen in init() functions before concurrent access.
20+
var providerRegistry sync.Map
2221

2322
// RegisterProviderType registers a provider type ID with its unmarshal function.
2423
// Type IDs must be globally unique (e.g. "openai.options").
24+
// This should only be called during package initialization (init functions).
2525
func RegisterProviderType(typeID string, unmarshalFn UnmarshalFunc) {
26-
registryMutex.Lock()
27-
defer registryMutex.Unlock()
28-
providerRegistry[typeID] = unmarshalFn
26+
providerRegistry.Store(typeID, unmarshalFn)
2927
}
3028

3129
// unmarshalProviderData routes a typed payload to the correct constructor.
@@ -35,14 +33,12 @@ func unmarshalProviderData(data []byte) (ProviderOptionsData, error) {
3533
return nil, err
3634
}
3735

38-
registryMutex.RLock()
39-
unmarshalFn, exists := providerRegistry[pj.Type]
40-
registryMutex.RUnlock()
41-
36+
val, exists := providerRegistry.Load(pj.Type)
4237
if !exists {
4338
return nil, fmt.Errorf("unknown provider data type: %s", pj.Type)
4439
}
4540

41+
unmarshalFn := val.(UnmarshalFunc)
4642
return unmarshalFn(pj.Data)
4743
}
4844

@@ -68,3 +64,43 @@ func UnmarshalProviderOptions(data map[string]json.RawMessage) (ProviderOptions,
6864
func UnmarshalProviderMetadata(data map[string]json.RawMessage) (ProviderMetadata, error) {
6965
return unmarshalProviderDataMap(data)
7066
}
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+
// }
77+
func 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+
// }
104+
func UnmarshalProviderType[T any](data []byte, target *T) error {
105+
return json.Unmarshal(data, target)
106+
}

providers/anthropic/provider_options.go

Lines changed: 37 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,31 @@ const (
1414
TypeProviderCacheControl = Name + ".cache_control_options"
1515
)
1616

17+
// Register Anthropic provider-specific types with the global registry.
18+
func init() {
19+
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
20+
var v ProviderOptions
21+
if err := json.Unmarshal(data, &v); err != nil {
22+
return nil, err
23+
}
24+
return &v, nil
25+
})
26+
fantasy.RegisterProviderType(TypeReasoningOptionMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
27+
var v ReasoningOptionMetadata
28+
if err := json.Unmarshal(data, &v); err != nil {
29+
return nil, err
30+
}
31+
return &v, nil
32+
})
33+
fantasy.RegisterProviderType(TypeProviderCacheControl, func(data []byte) (fantasy.ProviderOptionsData, error) {
34+
var v ProviderCacheControlOptions
35+
if err := json.Unmarshal(data, &v); err != nil {
36+
return nil, err
37+
}
38+
return &v, nil
39+
})
40+
}
41+
1742
// ProviderOptions represents additional options for the Anthropic provider.
1843
type ProviderOptions struct {
1944
SendReasoning *bool `json:"send_reasoning"`
@@ -27,28 +52,17 @@ func (o *ProviderOptions) Options() {}
2752
// MarshalJSON implements custom JSON marshaling with type info for ProviderOptions.
2853
func (o ProviderOptions) MarshalJSON() ([]byte, error) {
2954
type plain ProviderOptions
30-
raw, err := json.Marshal(plain(o))
31-
if err != nil {
32-
return nil, err
33-
}
34-
return json.Marshal(struct {
35-
Type string `json:"type"`
36-
Data json.RawMessage `json:"data"`
37-
}{
38-
Type: TypeProviderOptions,
39-
Data: raw,
40-
})
55+
return fantasy.MarshalProviderType(TypeProviderOptions, plain(o))
4156
}
4257

4358
// UnmarshalJSON implements custom JSON unmarshaling with type info for ProviderOptions.
4459
func (o *ProviderOptions) UnmarshalJSON(data []byte) error {
4560
type plain ProviderOptions
46-
var oo plain
47-
err := json.Unmarshal(data, &oo)
48-
if err != nil {
61+
var p plain
62+
if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
4963
return err
5064
}
51-
*o = ProviderOptions(oo)
65+
*o = ProviderOptions(p)
5266
return nil
5367
}
5468

@@ -69,28 +83,17 @@ func (*ReasoningOptionMetadata) Options() {}
6983
// MarshalJSON implements custom JSON marshaling with type info for ReasoningOptionMetadata.
7084
func (m ReasoningOptionMetadata) MarshalJSON() ([]byte, error) {
7185
type plain ReasoningOptionMetadata
72-
raw, err := json.Marshal(plain(m))
73-
if err != nil {
74-
return nil, err
75-
}
76-
return json.Marshal(struct {
77-
Type string `json:"type"`
78-
Data json.RawMessage `json:"data"`
79-
}{
80-
Type: TypeReasoningOptionMetadata,
81-
Data: raw,
82-
})
86+
return fantasy.MarshalProviderType(TypeReasoningOptionMetadata, plain(m))
8387
}
8488

8589
// UnmarshalJSON implements custom JSON unmarshaling with type info for ReasoningOptionMetadata.
8690
func (m *ReasoningOptionMetadata) UnmarshalJSON(data []byte) error {
8791
type plain ReasoningOptionMetadata
88-
var rm plain
89-
err := json.Unmarshal(data, &rm)
90-
if err != nil {
92+
var p plain
93+
if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
9194
return err
9295
}
93-
*m = ReasoningOptionMetadata(rm)
96+
*m = ReasoningOptionMetadata(p)
9497
return nil
9598
}
9699

@@ -105,28 +108,17 @@ func (*ProviderCacheControlOptions) Options() {}
105108
// MarshalJSON implements custom JSON marshaling with type info for ProviderCacheControlOptions.
106109
func (o ProviderCacheControlOptions) MarshalJSON() ([]byte, error) {
107110
type plain ProviderCacheControlOptions
108-
raw, err := json.Marshal(plain(o))
109-
if err != nil {
110-
return nil, err
111-
}
112-
return json.Marshal(struct {
113-
Type string `json:"type"`
114-
Data json.RawMessage `json:"data"`
115-
}{
116-
Type: TypeProviderCacheControl,
117-
Data: raw,
118-
})
111+
return fantasy.MarshalProviderType(TypeProviderCacheControl, plain(o))
119112
}
120113

121114
// UnmarshalJSON implements custom JSON unmarshaling with type info for ProviderCacheControlOptions.
122115
func (o *ProviderCacheControlOptions) UnmarshalJSON(data []byte) error {
123116
type plain ProviderCacheControlOptions
124-
var cc plain
125-
err := json.Unmarshal(data, &cc)
126-
if err != nil {
117+
var p plain
118+
if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
127119
return err
128120
}
129-
*o = ProviderCacheControlOptions(cc)
121+
*o = ProviderCacheControlOptions(p)
130122
return nil
131123
}
132124

@@ -157,28 +149,3 @@ func ParseOptions(data map[string]any) (*ProviderOptions, error) {
157149
}
158150
return &options, nil
159151
}
160-
161-
// Register Anthropic provider-specific types with the global registry.
162-
func init() {
163-
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
164-
var v ProviderOptions
165-
if err := json.Unmarshal(data, &v); err != nil {
166-
return nil, err
167-
}
168-
return &v, nil
169-
})
170-
fantasy.RegisterProviderType(TypeReasoningOptionMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
171-
var v ReasoningOptionMetadata
172-
if err := json.Unmarshal(data, &v); err != nil {
173-
return nil, err
174-
}
175-
return &v, nil
176-
})
177-
fantasy.RegisterProviderType(TypeProviderCacheControl, func(data []byte) (fantasy.ProviderOptionsData, error) {
178-
var v ProviderCacheControlOptions
179-
if err := json.Unmarshal(data, &v); err != nil {
180-
return nil, err
181-
}
182-
return &v, nil
183-
})
184-
}

0 commit comments

Comments
 (0)