use crate::error::LingerError;
use crate::RequestId;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateEvalRequest {
pub data_source_config: Value,
pub testing_criteria: Vec<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub metadata: BTreeMap<String, String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateEvalRequest {
pub fn builder() -> CreateEvalRequestBuilder {
CreateEvalRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateEvalRequestBuilder {
data_source_config: Option<Value>,
testing_criteria: Vec<Value>,
name: Option<String>,
metadata: BTreeMap<String, String>,
extra: BTreeMap<String, Value>,
}
impl CreateEvalRequestBuilder {
pub fn data_source_config(mut self, data_source_config: Value) -> Self {
self.data_source_config = Some(data_source_config);
self
}
pub fn testing_criterion(mut self, testing_criterion: Value) -> Self {
self.testing_criteria.push(testing_criterion);
self
}
pub fn testing_criteria(mut self, testing_criteria: impl IntoIterator<Item = Value>) -> Self {
self.testing_criteria = testing_criteria.into_iter().collect();
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateEvalRequest, LingerError> {
validate_optional_string("name", self.name.as_deref())?;
validate_metadata(&self.metadata)?;
validate_json_value("data_source_config", self.data_source_config.as_ref(), true)?;
validate_json_values("testing_criteria", &self.testing_criteria, true)?;
validate_extra_fields(&self.extra)?;
Ok(CreateEvalRequest {
data_source_config: self
.data_source_config
.expect("validated data_source_config"),
testing_criteria: self.testing_criteria,
name: self.name,
metadata: self.metadata,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ModifyEvalRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub metadata: BTreeMap<String, String>,
}
impl ModifyEvalRequest {
pub fn builder() -> ModifyEvalRequestBuilder {
ModifyEvalRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ModifyEvalRequestBuilder {
name: Option<String>,
metadata: BTreeMap<String, String>,
}
impl ModifyEvalRequestBuilder {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn build(self) -> Result<ModifyEvalRequest, LingerError> {
validate_optional_string("name", self.name.as_deref())?;
validate_metadata(&self.metadata)?;
Ok(ModifyEvalRequest {
name: self.name,
metadata: self.metadata,
})
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateEvalRunRequest {
pub data_source: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub metadata: BTreeMap<String, String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateEvalRunRequest {
pub fn builder() -> CreateEvalRunRequestBuilder {
CreateEvalRunRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateEvalRunRequestBuilder {
data_source: Option<Value>,
name: Option<String>,
metadata: BTreeMap<String, String>,
extra: BTreeMap<String, Value>,
}
impl CreateEvalRunRequestBuilder {
pub fn data_source(mut self, data_source: Value) -> Self {
self.data_source = Some(data_source);
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateEvalRunRequest, LingerError> {
validate_optional_string("name", self.name.as_deref())?;
validate_metadata(&self.metadata)?;
validate_json_value("data_source", self.data_source.as_ref(), true)?;
validate_extra_fields(&self.extra)?;
Ok(CreateEvalRunRequest {
data_source: self.data_source.expect("validated data_source"),
name: self.name,
metadata: self.metadata,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct Eval {
pub id: String,
pub object: String,
pub created_at: u64,
#[serde(default)]
pub name: String,
#[serde(default)]
pub metadata: BTreeMap<String, String>,
#[serde(default)]
pub data_source_config: Value,
#[serde(default)]
pub testing_criteria: Vec<Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl Eval {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct EvalRun {
pub id: String,
pub object: String,
pub created_at: u64,
pub eval_id: String,
pub status: String,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub model: Option<String>,
#[serde(default)]
pub metadata: BTreeMap<String, String>,
#[serde(default)]
pub data_source: Value,
#[serde(default)]
pub error: Option<Value>,
#[serde(default)]
pub per_model_usage: Vec<Value>,
#[serde(default)]
pub per_testing_criteria_results: Vec<Value>,
#[serde(default)]
pub report_url: Option<String>,
#[serde(default)]
pub result_counts: Value,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalRun {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct EvalRunPage {
pub object: String,
#[serde(default)]
pub data: Vec<EvalRun>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalRunPage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct EvalRunDeletion {
pub run_id: String,
pub object: String,
pub deleted: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalRunDeletion {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct EvalRunOutputItem {
pub id: String,
pub object: String,
pub created_at: u64,
pub eval_id: String,
pub run_id: String,
pub status: String,
pub datasource_item_id: u64,
#[serde(default)]
pub datasource_item: Value,
#[serde(default)]
pub results: Vec<Value>,
#[serde(default)]
pub sample: Value,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalRunOutputItem {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct EvalRunOutputItemPage {
pub object: String,
#[serde(default)]
pub data: Vec<EvalRunOutputItem>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalRunOutputItemPage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct EvalPage {
pub object: String,
#[serde(default)]
pub data: Vec<Eval>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalPage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct EvalDeletion {
pub eval_id: String,
pub object: String,
pub deleted: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl EvalDeletion {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
fn validate_json_value(
name: &str,
value: Option<&Value>,
require_present: bool,
) -> Result<(), LingerError> {
match value {
Some(value) if value.is_null() => Err(LingerError::invalid_config(format!(
"{name} must not be null"
))),
Some(_) => Ok(()),
None if require_present => Err(LingerError::invalid_config(format!("{name} is required"))),
None => Ok(()),
}
}
fn validate_json_values(
name: &str,
values: &[Value],
require_non_empty: bool,
) -> Result<(), LingerError> {
if require_non_empty && values.is_empty() {
return Err(LingerError::invalid_config(format!("{name} is required")));
}
if values.iter().any(Value::is_null) {
return Err(LingerError::invalid_config(format!(
"{name} must not contain null values"
)));
}
Ok(())
}
fn validate_metadata(metadata: &BTreeMap<String, String>) -> Result<(), LingerError> {
for key in metadata.keys() {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"metadata keys must not be empty",
));
}
}
Ok(())
}
fn validate_optional_string(name: &str, value: Option<&str>) -> Result<(), LingerError> {
if value.is_some_and(|value| value.trim().is_empty()) {
return Err(LingerError::invalid_config(format!(
"{name} must not be empty"
)));
}
Ok(())
}
fn validate_extra_fields(extra: &BTreeMap<String, Value>) -> Result<(), LingerError> {
for (key, value) in extra {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"extra field names must not be empty",
));
}
if value.is_null() {
return Err(LingerError::invalid_config(format!(
"extra field {key} must not be null"
)));
}
}
Ok(())
}