///|
pub(all) struct CookbookEntry {
id : String
title : String
json_text : String
path_text : String
output_mode : OutputMode
expected : String
note : String
} derive(Eq, Debug)
///|
pub(all) struct CookbookResult {
id : String
title : String
output : String
expected : String
passed : Bool
} derive(Eq, Debug)
///|
pub fn cookbook_entries() -> Array[CookbookEntry] {
[
{
id: "values-basic-members",
title: "Read all user names",
json_text: "{\"users\":[{\"name\":\"Ada\"},{\"name\":\"Grace\"}]}",
path_text: "$.users[*].name",
output_mode: Values,
expected: "[\"Ada\",\"Grace\"]",
note: "The common case for extracting a repeated field from an API response.",
},
{
id: "pointers-for-diagnostics",
title: "Return match locations instead of values",
json_text: "{\"users\":[{\"name\":\"Ada\"},{\"name\":\"Grace\"}]}",
path_text: "$.users[*].name",
output_mode: Pointers,
expected: "[\"/users/0/name\",\"/users/1/name\"]",
note: "Pointer output is useful when the caller wants to patch or report locations.",
},
{
id: "matches-for-tools",
title: "Return path and value pairs",
json_text: "{\"users\":[{\"name\":\"Ada\"},{\"name\":\"Grace\"}]}",
path_text: "$.users[*].name",
output_mode: Matches,
expected: "[{\"path\":\"/users/0/name\",\"value\":\"Ada\"},{\"path\":\"/users/1/name\",\"value\":\"Grace\"}]",
note: "Match objects are friendlier for tool calling and structured diagnostics.",
},
{
id: "quoted-config-keys",
title: "Read configuration keys containing punctuation",
json_text: "{\"compiler.options\":{\"warn-as-error\":true,\"target/backend\":\"wasm-gc\"}}",
path_text: "$['compiler.options']['target/backend']",
output_mode: Values,
expected: "[\"wasm-gc\"]",
note: "Quoted selectors avoid forcing config authors to avoid punctuation.",
},
{
id: "union-api-summary",
title: "Collect a small summary from an object",
json_text: "{\"package\":{\"name\":\"moonjsonpath\",\"version\":\"0.1.0\",\"private\":false}}",
path_text: "$.package['name','version']",
output_mode: Values,
expected: "[\"moonjsonpath\",\"0.1.0\"]",
note: "Union selectors preserve selector order and are compact in CLI workflows.",
},
{
id: "recursive-error-fields",
title: "Find nested error messages",
json_text: "{\"ok\":false,\"errors\":[{\"message\":\"bad token\"},{\"nested\":{\"message\":\"expired\"}}]}",
path_text: "$..message",
output_mode: Values,
expected: "[\"bad token\",\"expired\"]",
note: "Recursive descent helps when JSON shape varies across providers.",
},
{
id: "slice-pagination-preview",
title: "Preview the first page of results",
json_text: "{\"items\":[0,1,2,3,4,5]}",
path_text: "$.items[:3]",
output_mode: Values,
expected: "[0,1,2]",
note: "Slices are useful for CLI previews and test fixture minimization.",
},
{
id: "step-sampling",
title: "Sample every other item",
json_text: "{\"items\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}",
path_text: "$.items[::2]",
output_mode: Values,
expected: "[\"a\",\"c\",\"e\"]",
note: "Step slices keep the query small when sampling large arrays.",
},
{
id: "reverse-tail",
title: "Read latest entries first",
json_text: "{\"events\":[\"old\",\"mid\",\"new\"]}",
path_text: "$.events[::-1]",
output_mode: Values,
expected: "[\"new\",\"mid\",\"old\"]",
note: "Negative steps make log-like arrays easy to inspect from newest to oldest.",
},
{
id: "filter-existence",
title: "Keep objects that contain a key",
json_text: "{\"books\":[{\"title\":\"A\",\"isbn\":\"1\"},{\"title\":\"B\"},{\"title\":\"C\",\"isbn\":\"3\"}]}",
path_text: "$.books[?(@.isbn)].title",
output_mode: Values,
expected: "[\"A\",\"C\"]",
note: "Existence filters are a compact way to validate optional metadata.",
},
{
id: "filter-conjunction",
title: "Filter by two fields",
json_text: "{\"jobs\":[{\"name\":\"lint\",\"ok\":true,\"ms\":120},{\"name\":\"test\",\"ok\":true,\"ms\":900},{\"name\":\"pack\",\"ok\":false,\"ms\":50}]}",
path_text: "$.jobs[?(@.ok == true && @.ms < 500)].name",
output_mode: Values,
expected: "[\"lint\"]",
note: "Conjunction filters cover many CI and telemetry checks without becoming jq.",
},
{
id: "negative-index",
title: "Read the last element",
json_text: "{\"versions\":[\"0.1.0\",\"0.2.0\",\"0.3.0\"]}",
path_text: "$.versions[-1]",
output_mode: Values,
expected: "[\"0.3.0\"]",
note: "Negative indexes are common in JSONPath implementations and useful in release metadata.",
},
{
id: "pointer-escaped-output",
title: "Pointers escape slash and tilde",
json_text: "{\"a/b\":{\"m~n\":1}}",
path_text: "$['a/b']['m~n']",
output_mode: Pointers,
expected: "[\"/a~1b/m~0n\"]",
note: "Pointer output remains RFC 6901 compatible even for unusual object keys.",
},
{
id: "nested-author-filter",
title: "Filter through a nested field",
json_text: "{\"books\":[{\"title\":\"A\",\"author\":{\"name\":\"Ada\"}},{\"title\":\"B\",\"author\":{\"name\":\"Grace\"}}]}",
path_text: "$.books[?(@.author.name == \"Ada\")].title",
output_mode: Values,
expected: "[\"A\"]",
note: "Nested filters keep the first version useful for real API responses.",
},
{
id: "pretty-match-output",
title: "Pretty print match objects",
json_text: "{\"users\":[{\"name\":\"Ada\"}]}",
path_text: "$.users[*].name",
output_mode: Matches,
expected: "[\n {\n \"path\": \"/users/0/name\",\n \"value\": \"Ada\"\n }\n]",
note: "Pretty output is intended for humans reading CLI output.",
},
]
}
///|
pub fn CookbookEntry::run(
self : CookbookEntry,
) -> Result[CookbookResult, String] {
let indent = if self.id == "pretty-match-output" { 2 } else { 0 }
let output = match
query_json_text_with_options(self.path_text, self.json_text, {
output: self.output_mode,
indent,
}) {
Ok(output) => output
Err(message) => return Err(message)
}
Ok({
id: self.id,
title: self.title,
output,
expected: self.expected,
passed: output == self.expected,
})
}
///|
pub fn run_cookbook() -> Array[CookbookResult] {
cookbook_entries().map(entry => {
match entry.run() {
Ok(result) => result
Err(message) =>
{
id: entry.id,
title: entry.title,
output: message,
expected: entry.expected,
passed: false,
}
}
})
}
///|
pub fn cookbook_markdown() -> String {
let out = StringBuilder::new()
out.write_string("# MoonJSONPath Cookbook\n\n")
for entry in cookbook_entries() {
out.write_string("## ")
out.write_string(entry.title)
out.write_string("\n\n")
out.write_string(entry.note)
out.write_string("\n\n")
out.write_string("```text\n")
out.write_string(entry.path_text)
out.write_string("\n```\n\n")
out.write_string("Expected output:\n\n```json\n")
out.write_string(entry.expected)
out.write_string("\n```\n\n")
}
out.to_string()
}