Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mcp

Model Context Protocol server for Pudu language documentation and compiler tools

0.1.1Apache-2.01

InstallClose

Arguments.pudu

Pudu88 lines3.1 KB

GitHub ↗
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)] }1314/// Arguments checked against a tool's spec, or the first problem found.15export 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}4546/// A validated string argument.47export fn text(args: &Args, name: Str) -> Option[Str] {48  let held = List.lookup(&args.values, name) ?49  Json.asText(&held)50}5152/// A validated integer argument.53export fn integer(args: &Args, name: Str) -> Option[Int] {54  let held = List.lookup(&args.values, name) ?55  Json.asInt(&held)56}5758/// A validated integer argument, or a fallback when it was not given.59export fn integerOr(args: &Args, name: Str, fallback: Int) -> Int {60  match integer(args, name) {61    case Some(value) => value62    case None => fallback63  }64}6566/// The problem with one given argument, if any.67fn 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}8384/// A kind name with its indefinite article.85fn article(kind: Str) -> Str {86  if kind == "integer" { "an integer" } else { "a " + kind }87}88