
Resources.pudu
Pudu95 lines4.3 KB
1/** @App.Resources.Handler — documentation and reference as resources */2module PuduLangMcp.App.Resources34import Std.Json as Json5import PuduLangMcp.App.Context as Context6import PuduLangMcp.App.Tools.Reference as Reference7import PuduLangMcp.Constants.Protocol as Protocol8import PuduLangMcp.Constants.Server as Server9import PuduLangMcp.Domain.Docs.Chapter as Chapter10import PuduLangMcp.Domain.Protocol.Pagination as Pagination11import PuduLangMcp.Domain.Rpc.Reply as Reply12import PuduLangMcp.Errors.RpcError as RpcError13import PuduLangMcp.Errors.ToolError as ToolError14import PuduLangMcp.Utils.JsonAccess as Access15import PuduLangMcp.Utils.Uri as Uri161718export const INDEX_URI: Str = "pudu://docs/index"192021export const REFERENCE_PREFIX: Str = "pudu://reference/"222324const SCHEME: Str = "pudu://"252627const MARKDOWN: Str = "text/markdown"282930const PLAIN_TEXT: Str = "text/plain"313233export fn list(context: &Context.Context, params: &Json.Json) -> Result[Json.Json, RpcError.ProtocolError] {34 let cursor = Pagination.cursorOf(params) ?35 let all = [resource(INDEX_URI, "index", "Pudu documentation contents")].concat(context.chapters.map(|held: Chapter.Chapter| resource(SCHEME + held.group + "/" + held.slug, held.group + "/" + held.slug, held.title)))36 let paged = Pagination.page(&all, &cursor, Server.PAGE_SIZE) ?37 var fields = [("resources", Json.list(&paged[0]))]38 if let Some(next) = paged[1] { fields = fields.push(("nextCursor", Json.Text(next))) }39 Ok(Reply.complete(&fields, &Reply.Cached(Server.LIST_TTL_MS, Protocol.CACHE_PUBLIC)))40}414243export fn templates(context: &Context.Context, params: &Json.Json) -> Result[Json.Json, RpcError.ProtocolError] {44 let cursor = Pagination.cursorOf(params) ?45 let template = Json.object(&[46 ("uriTemplate", Json.Text(REFERENCE_PREFIX + "\{module\}")),47 ("name", Json.Text("reference")),48 ("title", Json.Text("Module reference")),49 ("description", Json.Text("Every public declaration of one standard-library or installed-package module.")),50 ("mimeType", Json.Text(PLAIN_TEXT))51 ])52 let paged = Pagination.page(&[template], &cursor, Server.PAGE_SIZE) ?53 Ok(Reply.complete(&[("resourceTemplates", Json.list(&paged[0]))], &Reply.Cached(Server.LIST_TTL_MS, Protocol.CACHE_PUBLIC)))54}555657export fn read(context: &Context.Context, params: &Json.Json) -> Result[Json.Json, RpcError.ProtocolError] {58 let uri = match Access.text(params, "uri") {59 case Some(given) => given60 case None => { return Err(RpcError.InvalidParams("uri must be a string")) }61 }62 if uri == INDEX_URI {63 return Ok(contents(uri, MARKDOWN, Chapter.tableOfContents(&context.chapters), Server.DOCS_TTL_MS, Protocol.CACHE_PUBLIC))64 }65 if let Some(moduleName) = Uri.resourcePath(uri, REFERENCE_PREFIX) {66 return match Reference.moduleText(context, moduleName) {67 case Ok(text) => Ok(contents(uri, PLAIN_TEXT, text, Server.STDLIB_TTL_MS, Protocol.CACHE_PRIVATE))68 case Err(ToolError.NotFound(_)) => Err(RpcError.ResourceNotFound(uri))69 case Err(failure) => Err(RpcError.Internal(ToolError.explain(&failure)))70 }71 }72 let path = match Uri.resourcePath(uri, SCHEME) {73 case Some(rest) => rest74 case None => { return Err(RpcError.ResourceNotFound(uri)) }75 }76 let slash = path.indexOf("/")77 if slash == -1 { return Err(RpcError.ResourceNotFound(uri)) }78 match Chapter.find(&context.chapters, path.take(slash), path.drop(slash + 1)) {79 case Some(held) => Ok(contents(uri, MARKDOWN, held.markdown, Server.DOCS_TTL_MS, Protocol.CACHE_PUBLIC))80 case None => Err(RpcError.ResourceNotFound(uri))81 }82}838485fn resource(uri: Str, name: Str, title: Str) -> Json.Json {86 Json.object(&[("uri", Json.Text(uri)), ("name", Json.Text(name)), ("title", Json.Text(title)), ("mimeType", Json.Text(MARKDOWN))])87}888990fn contents(uri: Str, mimeType: Str, text: Str, ttl: Int, visibility: Str) -> Json.Json {91 Reply.complete(&[92 ("contents", Json.list(&[Json.object(&[("uri", Json.Text(uri)), ("mimeType", Json.Text(mimeType)), ("text", Json.Text(text))])]))93 ], &Reply.Cached(ttl, visibility))94}95