
Arguments.pudu
Pudu88 lines3.1 KB
1/** @Domain.Catalog.Arguments.Module — validates tool arguments against specs */2module PuduLangMcp.Domain.Catalog.Arguments34import Std.Json as Json5import Std.List as List6import Std.Text as Text7import PuduLangMcp.Domain.Catalog.ToolSpec as ToolSpec8import PuduLangMcp.Errors.ToolError as ToolError9import PuduLangMcp.Utils.JsonAccess as Access1011/** @Domain.Catalog.Arguments.Args — validated arguments of one call */12export type Args = { values: Array[(Str, Json.Json)] }131415export fn validate(spec: &ToolSpec.ToolSpec, arguments: &Json.Json) -> Result[Args, ToolError.ToolFailure] {16 let given = match arguments {17 case Json.Null => []18 case Json.Object(fields) => fields19 case _ => { return Err(ToolError.NotAnObject) }20 }21 let declared = spec.properties.map(|held: ToolSpec.Property| held.name)22 for member in given {23 if !declared.contains(member[0]) { return Err(ToolError.UnknownArgument(member[0])) }24 }25 for property in spec.properties {26 match List.lookup(&given, property.name) {27 case None => {28 if property.required { return Err(ToolError.MissingArgument(property.name)) }29 }30 case Some(value) => {31 let problem = checked(&property, &value)32 match problem {33 case Some(failure) => { return Err(failure) }34 case None => ()35 }36 }37 }38 }39 if !spec.exactlyOne.isEmpty() {40 let present = spec.exactlyOne.filter(|name: Str| List.lookup(&given, name) != None).length()41 if present != 1 { return Err(ToolError.ExactlyOne(spec.exactlyOne)) }42 }43 Ok(Args{values: given})44}454647export fn text(args: &Args, name: Str) -> Option[Str] {48 let held = List.lookup(&args.values, name) ?49 Json.asText(&held)50}515253export fn integer(args: &Args, name: Str) -> Option[Int] {54 let held = List.lookup(&args.values, name) ?55 Json.asInt(&held)56}575859export fn integerOr(args: &Args, name: Str, fallback: Int) -> Int {60 match integer(args, name) {61 case Some(value) => value62 case None => fallback63 }64}656667fn checked(property: &ToolSpec.Property, value: &Json.Json) -> Option[ToolError.ToolFailure] {68 let expected = ToolSpec.kindName(&property.kind)69 if Access.kindOf(value) != expected { return Some(ToolError.WrongType(property.name, article(expected))) }70 match value {71 case Json.Text(content) => {72 if property.required && Text.isBlank(content) { return Some(ToolError.WrongType(property.name, "a non-empty string")) }73 None74 }75 case Json.Number(number) => {76 let low = match property.minimum { case Some(bound) => bound case None => number }77 let high = match property.maximum { case Some(bound) => bound case None => number }78 if number < low || number > high { Some(ToolError.OutOfRange(property.name, "between " + show(low) + " and " + show(high))) } else { None }79 }80 case _ => None81 }82}838485fn article(kind: Str) -> Str {86 if kind == "integer" { "an integer" } else { "a " + kind }87}88