Skip to main content

linger_openai_sdk/
evals.rs

1use crate::error::LingerError;
2use crate::RequestId;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::BTreeMap;
6
7/// EN: Request body for `POST /v1/evals`.
8/// 中文:`POST /v1/evals` 的请求体。
9#[derive(Clone, Debug, Serialize, PartialEq)]
10#[non_exhaustive]
11pub struct CreateEvalRequest {
12    /// EN: Data source configuration used by the eval.
13    /// 中文:eval 使用的数据源配置。
14    pub data_source_config: Value,
15    /// EN: Testing criteria that score eval runs.
16    /// 中文:用于给 eval run 评分的测试准则。
17    pub testing_criteria: Vec<Value>,
18    /// EN: Optional eval name.
19    /// 中文:可选的 eval 名称。
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub name: Option<String>,
22    /// EN: Optional metadata.
23    /// 中文:可选元数据。
24    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
25    pub metadata: BTreeMap<String, String>,
26    /// EN: Forward-compatible optional fields not yet covered by handwritten types.
27    /// 中文:手写类型尚未覆盖的前向兼容可选字段。
28    #[serde(flatten)]
29    pub extra: BTreeMap<String, Value>,
30}
31
32impl CreateEvalRequest {
33    /// EN: Starts building an eval creation request.
34    /// 中文:开始构建 eval 创建请求。
35    pub fn builder() -> CreateEvalRequestBuilder {
36        CreateEvalRequestBuilder::default()
37    }
38}
39
40/// EN: Builder for eval creation requests.
41/// 中文:eval 创建请求的构建器。
42#[derive(Clone, Debug, Default)]
43#[non_exhaustive]
44pub struct CreateEvalRequestBuilder {
45    data_source_config: Option<Value>,
46    testing_criteria: Vec<Value>,
47    name: Option<String>,
48    metadata: BTreeMap<String, String>,
49    extra: BTreeMap<String, Value>,
50}
51
52impl CreateEvalRequestBuilder {
53    /// EN: Sets the eval data source configuration.
54    /// 中文:设置 eval 数据源配置。
55    pub fn data_source_config(mut self, data_source_config: Value) -> Self {
56        self.data_source_config = Some(data_source_config);
57        self
58    }
59
60    /// EN: Adds one eval testing criterion.
61    /// 中文:添加一个 eval 测试准则。
62    pub fn testing_criterion(mut self, testing_criterion: Value) -> Self {
63        self.testing_criteria.push(testing_criterion);
64        self
65    }
66
67    /// EN: Replaces all eval testing criteria.
68    /// 中文:替换全部 eval 测试准则。
69    pub fn testing_criteria(mut self, testing_criteria: impl IntoIterator<Item = Value>) -> Self {
70        self.testing_criteria = testing_criteria.into_iter().collect();
71        self
72    }
73
74    /// EN: Sets the optional eval name.
75    /// 中文:设置可选的 eval 名称。
76    pub fn name(mut self, name: impl Into<String>) -> Self {
77        self.name = Some(name.into());
78        self
79    }
80
81    /// EN: Adds a metadata key/value pair.
82    /// 中文:添加一个元数据键值对。
83    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
84        self.metadata.insert(key.into(), value.into());
85        self
86    }
87
88    /// EN: Adds a forward-compatible JSON field.
89    /// 中文:添加一个前向兼容 JSON 字段。
90    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
91        self.extra.insert(name.into(), value);
92        self
93    }
94
95    /// EN: Builds and validates the request.
96    /// 中文:构建并校验请求。
97    pub fn build(self) -> Result<CreateEvalRequest, LingerError> {
98        validate_optional_string("name", self.name.as_deref())?;
99        validate_metadata(&self.metadata)?;
100        validate_json_value("data_source_config", self.data_source_config.as_ref(), true)?;
101        validate_json_values("testing_criteria", &self.testing_criteria, true)?;
102        validate_extra_fields(&self.extra)?;
103        Ok(CreateEvalRequest {
104            data_source_config: self
105                .data_source_config
106                .expect("validated data_source_config"),
107            testing_criteria: self.testing_criteria,
108            name: self.name,
109            metadata: self.metadata,
110            extra: self.extra,
111        })
112    }
113}
114
115/// EN: Request body for `POST /v1/evals/{eval_id}`.
116/// 中文:`POST /v1/evals/{eval_id}` 的请求体。
117#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
118#[non_exhaustive]
119pub struct ModifyEvalRequest {
120    /// EN: Updated eval name.
121    /// 中文:更新后的 eval 名称。
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub name: Option<String>,
124    /// EN: Updated metadata.
125    /// 中文:更新后的元数据。
126    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
127    pub metadata: BTreeMap<String, String>,
128}
129
130impl ModifyEvalRequest {
131    /// EN: Starts building an eval modification request.
132    /// 中文:开始构建 eval 修改请求。
133    pub fn builder() -> ModifyEvalRequestBuilder {
134        ModifyEvalRequestBuilder::default()
135    }
136}
137
138/// EN: Builder for eval modification requests.
139/// 中文:eval 修改请求的构建器。
140#[derive(Clone, Debug, Default)]
141#[non_exhaustive]
142pub struct ModifyEvalRequestBuilder {
143    name: Option<String>,
144    metadata: BTreeMap<String, String>,
145}
146
147impl ModifyEvalRequestBuilder {
148    /// EN: Sets the updated eval name.
149    /// 中文:设置更新后的 eval 名称。
150    pub fn name(mut self, name: impl Into<String>) -> Self {
151        self.name = Some(name.into());
152        self
153    }
154
155    /// EN: Adds an updated metadata key/value pair.
156    /// 中文:添加一个更新后的元数据键值对。
157    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
158        self.metadata.insert(key.into(), value.into());
159        self
160    }
161
162    /// EN: Builds and validates the request.
163    /// 中文:构建并校验请求。
164    pub fn build(self) -> Result<ModifyEvalRequest, LingerError> {
165        validate_optional_string("name", self.name.as_deref())?;
166        validate_metadata(&self.metadata)?;
167        Ok(ModifyEvalRequest {
168            name: self.name,
169            metadata: self.metadata,
170        })
171    }
172}
173
174/// EN: Request body for `POST /v1/evals/{eval_id}/runs`.
175/// 中文:`POST /v1/evals/{eval_id}/runs` 的请求体。
176#[derive(Clone, Debug, Serialize, PartialEq)]
177#[non_exhaustive]
178pub struct CreateEvalRunRequest {
179    /// EN: Details about the run data source.
180    /// 中文:run 数据源的详细信息。
181    pub data_source: Value,
182    /// EN: Optional run name.
183    /// 中文:可选的 run 名称。
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub name: Option<String>,
186    /// EN: Optional metadata.
187    /// 中文:可选元数据。
188    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
189    pub metadata: BTreeMap<String, String>,
190    /// EN: Forward-compatible optional fields not yet covered by handwritten types.
191    /// 中文:手写类型尚未覆盖的前向兼容可选字段。
192    #[serde(flatten)]
193    pub extra: BTreeMap<String, Value>,
194}
195
196impl CreateEvalRunRequest {
197    /// EN: Starts building an eval run creation request.
198    /// 中文:开始构建 eval run 创建请求。
199    pub fn builder() -> CreateEvalRunRequestBuilder {
200        CreateEvalRunRequestBuilder::default()
201    }
202}
203
204/// EN: Builder for eval run creation requests.
205/// 中文:eval run 创建请求的构建器。
206#[derive(Clone, Debug, Default)]
207#[non_exhaustive]
208pub struct CreateEvalRunRequestBuilder {
209    data_source: Option<Value>,
210    name: Option<String>,
211    metadata: BTreeMap<String, String>,
212    extra: BTreeMap<String, Value>,
213}
214
215impl CreateEvalRunRequestBuilder {
216    /// EN: Sets the run data source.
217    /// 中文:设置 run 数据源。
218    pub fn data_source(mut self, data_source: Value) -> Self {
219        self.data_source = Some(data_source);
220        self
221    }
222
223    /// EN: Sets the optional run name.
224    /// 中文:设置可选的 run 名称。
225    pub fn name(mut self, name: impl Into<String>) -> Self {
226        self.name = Some(name.into());
227        self
228    }
229
230    /// EN: Adds a metadata key/value pair.
231    /// 中文:添加一个元数据键值对。
232    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
233        self.metadata.insert(key.into(), value.into());
234        self
235    }
236
237    /// EN: Adds a forward-compatible JSON field.
238    /// 中文:添加一个前向兼容 JSON 字段。
239    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
240        self.extra.insert(name.into(), value);
241        self
242    }
243
244    /// EN: Builds and validates the request.
245    /// 中文:构建并校验请求。
246    pub fn build(self) -> Result<CreateEvalRunRequest, LingerError> {
247        validate_optional_string("name", self.name.as_deref())?;
248        validate_metadata(&self.metadata)?;
249        validate_json_value("data_source", self.data_source.as_ref(), true)?;
250        validate_extra_fields(&self.extra)?;
251        Ok(CreateEvalRunRequest {
252            data_source: self.data_source.expect("validated data_source"),
253            name: self.name,
254            metadata: self.metadata,
255            extra: self.extra,
256        })
257    }
258}
259
260/// EN: Eval object returned by the Evals API.
261/// 中文:Evals API 返回的 eval 对象。
262#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
263#[non_exhaustive]
264pub struct Eval {
265    /// EN: Eval id.
266    /// 中文:eval ID。
267    pub id: String,
268    /// EN: API object type.
269    /// 中文:API 对象类型。
270    pub object: String,
271    /// EN: Unix timestamp for creation.
272    /// 中文:创建时间的 Unix 时间戳。
273    pub created_at: u64,
274    /// EN: Eval name.
275    /// 中文:eval 名称。
276    #[serde(default)]
277    pub name: String,
278    /// EN: Metadata attached to the eval.
279    /// 中文:附加到 eval 的元数据。
280    #[serde(default)]
281    pub metadata: BTreeMap<String, String>,
282    /// EN: Data source configuration returned by the API.
283    /// 中文:API 返回的数据源配置。
284    #[serde(default)]
285    pub data_source_config: Value,
286    /// EN: Testing criteria returned by the API.
287    /// 中文:API 返回的测试准则。
288    #[serde(default)]
289    pub testing_criteria: Vec<Value>,
290    /// EN: Additional fields preserved for forward compatibility.
291    /// 中文:为前向兼容保留的额外字段。
292    #[serde(flatten)]
293    pub extra: BTreeMap<String, Value>,
294    /// EN: OpenAI request id from response headers.
295    /// 中文:响应头中的 OpenAI 请求 ID。
296    #[serde(skip)]
297    request_id: Option<RequestId>,
298}
299
300impl Eval {
301    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
302        self.request_id = request_id;
303        self
304    }
305
306    /// EN: Returns the OpenAI request id, when present.
307    /// 中文:返回 OpenAI 请求 ID,如存在。
308    pub fn request_id(&self) -> Option<&RequestId> {
309        self.request_id.as_ref()
310    }
311}
312
313/// EN: Eval run object returned by the Evals API.
314/// 中文:Evals API 返回的 eval run 对象。
315#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
316#[non_exhaustive]
317pub struct EvalRun {
318    /// EN: Eval run id.
319    /// 中文:eval run ID。
320    pub id: String,
321    /// EN: API object type.
322    /// 中文:API 对象类型。
323    pub object: String,
324    /// EN: Unix timestamp for creation.
325    /// 中文:创建时间的 Unix 时间戳。
326    pub created_at: u64,
327    /// EN: Parent eval id.
328    /// 中文:父级 eval ID。
329    pub eval_id: String,
330    /// EN: Eval run status.
331    /// 中文:eval run 状态。
332    pub status: String,
333    /// EN: Eval run name.
334    /// 中文:eval run 名称。
335    #[serde(default)]
336    pub name: Option<String>,
337    /// EN: Model evaluated by the run, when applicable.
338    /// 中文:run 评估的模型,如适用。
339    #[serde(default)]
340    pub model: Option<String>,
341    /// EN: Metadata attached to the run.
342    /// 中文:附加到 run 的元数据。
343    #[serde(default)]
344    pub metadata: BTreeMap<String, String>,
345    /// EN: Run data source returned by the API.
346    /// 中文:API 返回的 run 数据源。
347    #[serde(default)]
348    pub data_source: Value,
349    /// EN: Run error information, when returned.
350    /// 中文:响应中存在时的 run 错误信息。
351    #[serde(default)]
352    pub error: Option<Value>,
353    /// EN: Usage per model, when returned.
354    /// 中文:响应中存在时的逐模型用量。
355    #[serde(default)]
356    pub per_model_usage: Vec<Value>,
357    /// EN: Results per testing criterion, when returned.
358    /// 中文:响应中存在时的逐测试准则结果。
359    #[serde(default)]
360    pub per_testing_criteria_results: Vec<Value>,
361    /// EN: Dashboard report URL, when returned.
362    /// 中文:响应中存在时的控制台报告 URL。
363    #[serde(default)]
364    pub report_url: Option<String>,
365    /// EN: Aggregate result counters, when returned.
366    /// 中文:响应中存在时的汇总结果计数。
367    #[serde(default)]
368    pub result_counts: Value,
369    /// EN: Additional fields preserved for forward compatibility.
370    /// 中文:为前向兼容保留的额外字段。
371    #[serde(flatten)]
372    pub extra: BTreeMap<String, Value>,
373    /// EN: OpenAI request id from response headers.
374    /// 中文:响应头中的 OpenAI 请求 ID。
375    #[serde(skip)]
376    request_id: Option<RequestId>,
377}
378
379impl EvalRun {
380    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
381        self.request_id = request_id;
382        self
383    }
384
385    /// EN: Returns the OpenAI request id, when present.
386    /// 中文:返回 OpenAI 请求 ID,如存在。
387    pub fn request_id(&self) -> Option<&RequestId> {
388        self.request_id.as_ref()
389    }
390}
391
392/// EN: Paginated eval run list returned by the Evals API.
393/// 中文:Evals API 返回的分页 eval run 列表。
394#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
395#[non_exhaustive]
396pub struct EvalRunPage {
397    /// EN: API list object type.
398    /// 中文:API 列表对象类型。
399    pub object: String,
400    /// EN: Runs on this page.
401    /// 中文:本页 run。
402    #[serde(default)]
403    pub data: Vec<EvalRun>,
404    /// EN: First run id on this page.
405    /// 中文:本页第一个 run ID。
406    #[serde(default)]
407    pub first_id: Option<String>,
408    /// EN: Last run id on this page.
409    /// 中文:本页最后一个 run ID。
410    #[serde(default)]
411    pub last_id: Option<String>,
412    /// EN: Whether more runs are available.
413    /// 中文:是否还有更多 run。
414    pub has_more: bool,
415    /// EN: OpenAI request id from response headers.
416    /// 中文:响应头中的 OpenAI 请求 ID。
417    #[serde(skip)]
418    request_id: Option<RequestId>,
419}
420
421impl EvalRunPage {
422    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
423        self.request_id = request_id;
424        self
425    }
426
427    /// EN: Returns the OpenAI request id, when present.
428    /// 中文:返回 OpenAI 请求 ID,如存在。
429    pub fn request_id(&self) -> Option<&RequestId> {
430        self.request_id.as_ref()
431    }
432}
433
434/// EN: Deletion result returned by the Eval Runs API.
435/// 中文:Eval Runs API 返回的删除结果。
436#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
437#[non_exhaustive]
438pub struct EvalRunDeletion {
439    /// EN: Deleted eval run id.
440    /// 中文:已删除的 eval run ID。
441    pub run_id: String,
442    /// EN: API object type.
443    /// 中文:API 对象类型。
444    pub object: String,
445    /// EN: Whether the run was deleted.
446    /// 中文:run 是否已删除。
447    pub deleted: bool,
448    /// EN: OpenAI request id from response headers.
449    /// 中文:响应头中的 OpenAI 请求 ID。
450    #[serde(skip)]
451    request_id: Option<RequestId>,
452}
453
454impl EvalRunDeletion {
455    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
456        self.request_id = request_id;
457        self
458    }
459
460    /// EN: Returns the OpenAI request id, when present.
461    /// 中文:返回 OpenAI 请求 ID,如存在。
462    pub fn request_id(&self) -> Option<&RequestId> {
463        self.request_id.as_ref()
464    }
465}
466
467/// EN: Eval run output item returned by the Evals API.
468/// 中文:Evals API 返回的 eval run output item。
469#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
470#[non_exhaustive]
471pub struct EvalRunOutputItem {
472    /// EN: Output item id.
473    /// 中文:output item ID。
474    pub id: String,
475    /// EN: API object type.
476    /// 中文:API 对象类型。
477    pub object: String,
478    /// EN: Unix timestamp for creation.
479    /// 中文:创建时间的 Unix 时间戳。
480    pub created_at: u64,
481    /// EN: Parent eval id.
482    /// 中文:父级 eval ID。
483    pub eval_id: String,
484    /// EN: Parent eval run id.
485    /// 中文:父级 eval run ID。
486    pub run_id: String,
487    /// EN: Output item status.
488    /// 中文:output item 状态。
489    pub status: String,
490    /// EN: Identifier for the source data item.
491    /// 中文:源数据项的标识符。
492    pub datasource_item_id: u64,
493    /// EN: Source data item details.
494    /// 中文:源数据项详情。
495    #[serde(default)]
496    pub datasource_item: Value,
497    /// EN: Grader results for this output item.
498    /// 中文:此 output item 的评分器结果。
499    #[serde(default)]
500    pub results: Vec<Value>,
501    /// EN: Input and output sample details.
502    /// 中文:输入和输出样本详情。
503    #[serde(default)]
504    pub sample: Value,
505    /// EN: Additional fields preserved for forward compatibility.
506    /// 中文:为前向兼容保留的额外字段。
507    #[serde(flatten)]
508    pub extra: BTreeMap<String, Value>,
509    /// EN: OpenAI request id from response headers.
510    /// 中文:响应头中的 OpenAI 请求 ID。
511    #[serde(skip)]
512    request_id: Option<RequestId>,
513}
514
515impl EvalRunOutputItem {
516    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
517        self.request_id = request_id;
518        self
519    }
520
521    /// EN: Returns the OpenAI request id, when present.
522    /// 中文:返回 OpenAI 请求 ID,如存在。
523    pub fn request_id(&self) -> Option<&RequestId> {
524        self.request_id.as_ref()
525    }
526}
527
528/// EN: Paginated eval run output item list returned by the Evals API.
529/// 中文:Evals API 返回的分页 eval run output item 列表。
530#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
531#[non_exhaustive]
532pub struct EvalRunOutputItemPage {
533    /// EN: API list object type.
534    /// 中文:API 列表对象类型。
535    pub object: String,
536    /// EN: Output items on this page.
537    /// 中文:本页 output item。
538    #[serde(default)]
539    pub data: Vec<EvalRunOutputItem>,
540    /// EN: First output item id on this page.
541    /// 中文:本页第一个 output item ID。
542    #[serde(default)]
543    pub first_id: Option<String>,
544    /// EN: Last output item id on this page.
545    /// 中文:本页最后一个 output item ID。
546    #[serde(default)]
547    pub last_id: Option<String>,
548    /// EN: Whether more output items are available.
549    /// 中文:是否还有更多 output item。
550    pub has_more: bool,
551    /// EN: OpenAI request id from response headers.
552    /// 中文:响应头中的 OpenAI 请求 ID。
553    #[serde(skip)]
554    request_id: Option<RequestId>,
555}
556
557impl EvalRunOutputItemPage {
558    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
559        self.request_id = request_id;
560        self
561    }
562
563    /// EN: Returns the OpenAI request id, when present.
564    /// 中文:返回 OpenAI 请求 ID,如存在。
565    pub fn request_id(&self) -> Option<&RequestId> {
566        self.request_id.as_ref()
567    }
568}
569
570/// EN: Paginated eval list returned by the Evals API.
571/// 中文:Evals API 返回的分页 eval 列表。
572#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
573#[non_exhaustive]
574pub struct EvalPage {
575    /// EN: API list object type.
576    /// 中文:API 列表对象类型。
577    pub object: String,
578    /// EN: Evals on this page.
579    /// 中文:本页 eval。
580    #[serde(default)]
581    pub data: Vec<Eval>,
582    /// EN: First eval id on this page.
583    /// 中文:本页第一个 eval ID。
584    #[serde(default)]
585    pub first_id: Option<String>,
586    /// EN: Last eval id on this page.
587    /// 中文:本页最后一个 eval ID。
588    #[serde(default)]
589    pub last_id: Option<String>,
590    /// EN: Whether more evals are available.
591    /// 中文:是否还有更多 eval。
592    pub has_more: bool,
593    /// EN: OpenAI request id from response headers.
594    /// 中文:响应头中的 OpenAI 请求 ID。
595    #[serde(skip)]
596    request_id: Option<RequestId>,
597}
598
599impl EvalPage {
600    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
601        self.request_id = request_id;
602        self
603    }
604
605    /// EN: Returns the OpenAI request id, when present.
606    /// 中文:返回 OpenAI 请求 ID,如存在。
607    pub fn request_id(&self) -> Option<&RequestId> {
608        self.request_id.as_ref()
609    }
610}
611
612/// EN: Deletion result returned by the Evals API.
613/// 中文:Evals API 返回的删除结果。
614#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
615#[non_exhaustive]
616pub struct EvalDeletion {
617    /// EN: Deleted eval id.
618    /// 中文:已删除的 eval ID。
619    pub eval_id: String,
620    /// EN: API object type.
621    /// 中文:API 对象类型。
622    pub object: String,
623    /// EN: Whether the eval was deleted.
624    /// 中文:eval 是否已删除。
625    pub deleted: bool,
626    /// EN: OpenAI request id from response headers.
627    /// 中文:响应头中的 OpenAI 请求 ID。
628    #[serde(skip)]
629    request_id: Option<RequestId>,
630}
631
632impl EvalDeletion {
633    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
634        self.request_id = request_id;
635        self
636    }
637
638    /// EN: Returns the OpenAI request id, when present.
639    /// 中文:返回 OpenAI 请求 ID,如存在。
640    pub fn request_id(&self) -> Option<&RequestId> {
641        self.request_id.as_ref()
642    }
643}
644
645fn validate_json_value(
646    name: &str,
647    value: Option<&Value>,
648    require_present: bool,
649) -> Result<(), LingerError> {
650    match value {
651        Some(value) if value.is_null() => Err(LingerError::invalid_config(format!(
652            "{name} must not be null"
653        ))),
654        Some(_) => Ok(()),
655        None if require_present => Err(LingerError::invalid_config(format!("{name} is required"))),
656        None => Ok(()),
657    }
658}
659
660fn validate_json_values(
661    name: &str,
662    values: &[Value],
663    require_non_empty: bool,
664) -> Result<(), LingerError> {
665    if require_non_empty && values.is_empty() {
666        return Err(LingerError::invalid_config(format!("{name} is required")));
667    }
668    if values.iter().any(Value::is_null) {
669        return Err(LingerError::invalid_config(format!(
670            "{name} must not contain null values"
671        )));
672    }
673    Ok(())
674}
675
676fn validate_metadata(metadata: &BTreeMap<String, String>) -> Result<(), LingerError> {
677    for key in metadata.keys() {
678        if key.trim().is_empty() {
679            return Err(LingerError::invalid_config(
680                "metadata keys must not be empty",
681            ));
682        }
683    }
684    Ok(())
685}
686
687fn validate_optional_string(name: &str, value: Option<&str>) -> Result<(), LingerError> {
688    if value.is_some_and(|value| value.trim().is_empty()) {
689        return Err(LingerError::invalid_config(format!(
690            "{name} must not be empty"
691        )));
692    }
693    Ok(())
694}
695
696fn validate_extra_fields(extra: &BTreeMap<String, Value>) -> Result<(), LingerError> {
697    for (key, value) in extra {
698        if key.trim().is_empty() {
699            return Err(LingerError::invalid_config(
700                "extra field names must not be empty",
701            ));
702        }
703        if value.is_null() {
704            return Err(LingerError::invalid_config(format!(
705                "extra field {key} must not be null"
706            )));
707        }
708    }
709    Ok(())
710}