use std::borrow::Cow;
use std::fmt;
use nutype::nutype;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
pub const CHALLENGE_NAME_ERROR_MESSAGE: &str = "challenge_name must be 3-63 lowercase ASCII letters, digits, or single hyphens, and must start and end with a letter or digit";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChallengeNameError;
impl fmt::Display for ChallengeNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(CHALLENGE_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for ChallengeNameError {}
pub const TARGET_NAME_ERROR_MESSAGE: &str = "target must be non-empty and contain only ASCII letters, digits, underscores, hyphens, or dots";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TargetNameError;
impl fmt::Display for TargetNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(TARGET_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for TargetNameError {}
pub const METRIC_NAME_ERROR_MESSAGE: &str = "metric_name must be non-empty and contain only ASCII letters, digits, underscores, hyphens, or dots";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MetricNameError;
impl fmt::Display for MetricNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(METRIC_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for MetricNameError {}
pub const ASSET_NAME_ERROR_MESSAGE: &str = "asset_name must be non-empty and contain only ASCII letters, digits, underscores, hyphens, or dots";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssetNameError;
impl fmt::Display for AssetNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(ASSET_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for AssetNameError {}
pub const RUN_NAME_ERROR_MESSAGE: &str = "run_name must be non-empty, must not be `.` or `..`, and must contain only ASCII letters, digits, underscores, hyphens, or dots";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RunNameError;
impl fmt::Display for RunNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(RUN_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for RunNameError {}
pub const RESOURCE_PROFILE_NAME_ERROR_MESSAGE: &str = "resource_profile.name must be non-empty and contain only ASCII letters, digits, underscores, hyphens, or dots";
pub const CHALLENGE_KEYWORD_ERROR_MESSAGE: &str = "challenge keyword must be non-empty after trimming, at most 30 UTF-8 bytes, and must not contain control characters";
pub const MOLTBOOK_SUBMOLT_NAME_ERROR_MESSAGE: &str = "moltbook submolt name must be 2-30 lowercase ASCII letters, digits, or single hyphens, and must start and end with a letter or digit";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceProfileNameError;
impl fmt::Display for ResourceProfileNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(RESOURCE_PROFILE_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for ResourceProfileNameError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChallengeKeywordError;
impl fmt::Display for ChallengeKeywordError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(CHALLENGE_KEYWORD_ERROR_MESSAGE)
}
}
impl std::error::Error for ChallengeKeywordError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoltbookSubmoltNameError;
impl fmt::Display for MoltbookSubmoltNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(MOLTBOOK_SUBMOLT_NAME_ERROR_MESSAGE)
}
}
impl std::error::Error for MoltbookSubmoltNameError {}
#[nutype(
sanitize(trim, lowercase),
validate(with = validate_challenge_name, error = ChallengeNameError),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct ChallengeName(String);
impl ChallengeName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
validate(with = validate_target_name, error = TargetNameError),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct TargetName(String);
impl TargetName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
validate(with = validate_asset_name, error = AssetNameError),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct AssetName(String);
impl AssetName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
validate(with = validate_run_name, error = RunNameError),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct RunName(String);
impl RunName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
validate(
with = validate_resource_profile_name,
error = ResourceProfileNameError
),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct ResourceProfileName(String);
impl ResourceProfileName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
sanitize(trim),
validate(with = validate_challenge_keyword, error = ChallengeKeywordError),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct ChallengeKeyword(String);
impl ChallengeKeyword {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
sanitize(trim, lowercase),
validate(
with = validate_moltbook_submolt_name,
error = MoltbookSubmoltNameError
),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct MoltbookSubmoltName(String);
impl MoltbookSubmoltName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
#[nutype(
sanitize(trim),
validate(with = validate_metric_name, error = MetricNameError),
derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
Serialize,
Deserialize,
FromStr,
TryFrom,
),
)]
pub struct MetricName(String);
impl MetricName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
#[allow(
clippy::panic,
reason = "the built-in `score` metric name is a hard-coded valid literal"
)]
pub fn score() -> Self {
match Self::try_new("score".to_string()) {
Ok(metric_name) => metric_name,
Err(_) => panic!("built-in metric name `score` must be valid"),
}
}
}
impl JsonSchema for ChallengeName {
fn inline_schema() -> bool {
true
}
fn schema_name() -> Cow<'static, str> {
"ChallengeName".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"minLength": 3,
"maxLength": 63,
"pattern": "^[a-z0-9](?:[a-z0-9]|-(?!-)){1,61}[a-z0-9]$"
})
}
}
macro_rules! impl_token_json_schema {
($type_name:ident, $schema_name:literal) => {
impl JsonSchema for $type_name {
fn inline_schema() -> bool {
true
}
fn schema_name() -> Cow<'static, str> {
$schema_name.into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"minLength": 1,
"pattern": "^[A-Za-z0-9_.-]+$"
})
}
}
};
}
impl_token_json_schema!(TargetName, "TargetName");
impl_token_json_schema!(MetricName, "MetricName");
impl_token_json_schema!(AssetName, "AssetName");
impl_token_json_schema!(ResourceProfileName, "ResourceProfileName");
impl JsonSchema for MoltbookSubmoltName {
fn inline_schema() -> bool {
true
}
fn schema_name() -> Cow<'static, str> {
"MoltbookSubmoltName".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"minLength": 2,
"maxLength": 30,
"pattern": "^[a-z0-9](?:[a-z0-9]|-(?!-)){0,28}[a-z0-9]$"
})
}
}
impl JsonSchema for RunName {
fn inline_schema() -> bool {
true
}
fn schema_name() -> Cow<'static, str> {
"RunName".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"minLength": 1,
"not": {
"enum": [".", ".."]
},
"pattern": "^[A-Za-z0-9_.-]+$"
})
}
}
impl JsonSchema for ChallengeKeyword {
fn inline_schema() -> bool {
true
}
fn schema_name() -> Cow<'static, str> {
"ChallengeKeyword".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"minLength": 1,
"maxLength": 30,
"description": "Public challenge keyword. Runtime validation enforces a 30 UTF-8 byte maximum and rejects control characters."
})
}
}
pub fn is_valid_challenge_name(value: &str) -> bool {
let bytes = value.as_bytes();
if !(3..=63).contains(&bytes.len()) {
return false;
}
let (Some(first), Some(last)) = (bytes.first(), bytes.last()) else {
return false;
};
if !first.is_ascii_alphanumeric() || !last.is_ascii_alphanumeric() {
return false;
}
if value.contains("--") {
return false;
}
bytes
.iter()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
}
fn has_name_token_syntax(value: &str) -> bool {
!value.is_empty()
&& value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
}
fn validate_challenge_name(value: &str) -> Result<(), ChallengeNameError> {
if is_valid_challenge_name(value) {
Ok(())
} else {
Err(ChallengeNameError)
}
}
fn validate_target_name(value: &str) -> Result<(), TargetNameError> {
if has_name_token_syntax(value) {
Ok(())
} else {
Err(TargetNameError)
}
}
fn validate_metric_name(value: &str) -> Result<(), MetricNameError> {
if has_name_token_syntax(value) {
Ok(())
} else {
Err(MetricNameError)
}
}
fn validate_asset_name(value: &str) -> Result<(), AssetNameError> {
if has_name_token_syntax(value) {
Ok(())
} else {
Err(AssetNameError)
}
}
fn validate_run_name(value: &str) -> Result<(), RunNameError> {
if has_name_token_syntax(value) && !matches!(value, "." | "..") {
Ok(())
} else {
Err(RunNameError)
}
}
fn validate_resource_profile_name(value: &str) -> Result<(), ResourceProfileNameError> {
if has_name_token_syntax(value) {
Ok(())
} else {
Err(ResourceProfileNameError)
}
}
fn validate_challenge_keyword(value: &str) -> Result<(), ChallengeKeywordError> {
if value.is_empty() || value.len() > 30 || value.chars().any(char::is_control) {
Err(ChallengeKeywordError)
} else {
Ok(())
}
}
fn validate_moltbook_submolt_name(value: &str) -> Result<(), MoltbookSubmoltNameError> {
let bytes = value.as_bytes();
if !(2..=30).contains(&bytes.len()) {
return Err(MoltbookSubmoltNameError);
}
let (Some(first), Some(last)) = (bytes.first(), bytes.last()) else {
return Err(MoltbookSubmoltNameError);
};
if !first.is_ascii_alphanumeric() || !last.is_ascii_alphanumeric() {
return Err(MoltbookSubmoltNameError);
}
if value.contains("--")
|| !bytes
.iter()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
{
return Err(MoltbookSubmoltNameError);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{
AssetName, ChallengeKeyword, ChallengeName, MetricName, MoltbookSubmoltName,
ResourceProfileName, RunName, TargetName, is_valid_challenge_name,
};
#[test]
fn validates_challenge_names() {
assert!(is_valid_challenge_name("sample-sum"));
assert!(ChallengeName::try_new("matrix-multiplication").is_ok());
let canonical = ChallengeName::try_new(" Matrix-Multiplication ")
.expect("challenge names should be lowercased and trimmed");
assert_eq!(canonical.as_str(), "matrix-multiplication");
assert!(ChallengeName::try_new("Bad_ID").is_err());
assert!(ChallengeName::try_new("-bad").is_err());
assert!(ChallengeName::try_new("bad-").is_err());
assert!(ChallengeName::try_new("bad--id").is_err());
assert!(ChallengeName::try_new("ab").is_err());
assert!(ChallengeName::try_new("matrix mult").is_err());
}
#[test]
fn validates_token_names() {
for value in ["linux-arm64-cpu", "score.v1", "cuda_12"] {
assert!(TargetName::try_new(value).is_ok());
assert!(MetricName::try_new(value).is_ok());
assert!(AssetName::try_new(value).is_ok());
assert!(RunName::try_new(value).is_ok());
assert!(ResourceProfileName::try_new(value).is_ok());
}
for value in ["", "linux arm64", "linux/arm64", "bad\ntarget"] {
assert!(TargetName::try_new(value).is_err());
assert!(MetricName::try_new(value).is_err());
assert!(AssetName::try_new(value).is_err());
assert!(RunName::try_new(value).is_err());
assert!(ResourceProfileName::try_new(value).is_err());
}
for value in [".", ".."] {
assert!(TargetName::try_new(value).is_ok());
assert!(MetricName::try_new(value).is_ok());
assert!(AssetName::try_new(value).is_ok());
assert!(RunName::try_new(value).is_err());
assert!(ResourceProfileName::try_new(value).is_ok());
}
let metric = MetricName::try_new(" runtime_ms ").expect("metric names trim edge spaces");
assert_eq!(metric.as_str(), "runtime_ms");
assert!(MetricName::try_new("runtime ms").is_err());
}
#[test]
fn serde_rejects_invalid_names() {
let challenge: ChallengeName =
serde_json::from_str("\"sample-sum\"").expect("valid challenge name should parse");
assert_eq!(challenge.as_str(), "sample-sum");
let challenge: ChallengeName =
serde_json::from_str("\" Sample-Sum \"").expect("challenge name should canonicalize");
assert_eq!(challenge.as_str(), "sample-sum");
assert!(serde_json::from_str::<ChallengeName>("\"sample sum\"").is_err());
let target: TargetName =
serde_json::from_str("\"linux-arm64-cpu\"").expect("valid target should parse");
assert_eq!(target.as_str(), "linux-arm64-cpu");
assert!(serde_json::from_str::<TargetName>("\"linux arm64\"").is_err());
let metric: MetricName =
serde_json::from_str("\"runtime_ms\"").expect("valid metric name should parse");
assert_eq!(metric.as_str(), "runtime_ms");
let metric: MetricName =
serde_json::from_str("\" runtime_ms \"").expect("metric name should trim");
assert_eq!(metric.as_str(), "runtime_ms");
assert!(serde_json::from_str::<MetricName>("\"runtime ms\"").is_err());
}
#[test]
fn validates_challenge_keywords() {
let keyword = ChallengeKeyword::try_new(" protein folding ")
.expect("keyword phrases should trim edge whitespace");
assert_eq!(keyword.as_str(), "protein folding");
assert!(ChallengeKeyword::try_new("AI".to_string()).is_ok());
assert!(ChallengeKeyword::try_new("图搜索".to_string()).is_ok());
assert!(ChallengeKeyword::try_new("".to_string()).is_err());
assert!(ChallengeKeyword::try_new("bad\nkeyword".to_string()).is_err());
assert!(ChallengeKeyword::try_new("abcdefghijklmnopqrstuvwxyz12345".to_string()).is_err());
assert!(ChallengeKeyword::try_new("多字节多字节多字节多字节".to_string()).is_err());
}
#[test]
fn validates_moltbook_submolt_names() {
let name = MoltbookSubmoltName::try_new(" Agentics-Platform ".to_string())
.expect("submolt name should canonicalize");
assert_eq!(name.as_str(), "agentics-platform");
assert!(MoltbookSubmoltName::try_new("ai".to_string()).is_ok());
assert!(MoltbookSubmoltName::try_new("a".to_string()).is_err());
assert!(MoltbookSubmoltName::try_new("-agentics".to_string()).is_err());
assert!(MoltbookSubmoltName::try_new("agentics-".to_string()).is_err());
assert!(MoltbookSubmoltName::try_new("agentics--platform".to_string()).is_err());
assert!(MoltbookSubmoltName::try_new("agentics_platform".to_string()).is_err());
}
}