// elicitation.mbt — upstream `src/elicitation.ts` mirror: the lody extension
// of standard ACP `elicitation/create`, mounted at `_meta.lody.elicitation`.
// The same shape is used at request, property, and enum-option scope; decode
// does not distinguish scopes — consumers read only the fields meaningful at
// their scope (upstream source doc comment).
///|
/// `LodyElicitationAnswer` — `string | string[]`: a single choice or a
/// multi-select choice set.
pub(all) enum LodyElicitationAnswer {
Single(String)
Multi(Array[String])
} derive(Eq, Debug)
///|
pub fn LodyElicitationAnswer::to_json(self : LodyElicitationAnswer) -> Json {
match self {
Single(answer) => Json::string(answer)
Multi(answers) =>
Json::array(answers.map(fn(answer) { Json::string(answer) }))
}
}
///|
pub fn LodyElicitationAnswer::from_json(
raw : Json,
) -> Result[LodyElicitationAnswer, String] {
let ctx = "lody.elicitation.answer"
match raw {
Json::String(answer) => Ok(Single(answer))
Json::Array(items) => {
let answers : Array[String] = []
for item in items {
match item {
Json::String(answer) => answers.push(answer)
_ => return Err("\{ctx}: every multi-select answer must be a string")
}
}
Ok(Multi(answers))
}
_ => Err("\{ctx} must be a string or an array of strings")
}
}
///|
/// `LodyElicitationOption` — one enum option (label required).
pub(all) struct LodyElicitationOption {
label : String
description : String?
preview : String?
} derive(Eq, Debug)
///|
pub fn LodyElicitationOption::to_json(self : LodyElicitationOption) -> Json {
let pairs : Array[(String, Json)] = [("label", Json::string(self.label))]
match self.description {
Some(v) => pairs.push(("description", Json::string(v)))
None => ()
}
match self.preview {
Some(v) => pairs.push(("preview", Json::string(v)))
None => ()
}
Json::object(Map::from_array(pairs))
}
///|
pub fn LodyElicitationOption::from_json(
raw : Json,
) -> Result[LodyElicitationOption, String] {
try {
let ctx = "lody.elicitation.option"
let fields = object_fields(raw, ctx)
Ok(LodyElicitationOption::{
label: req_string(fields, ctx, "label"),
description: opt_string(fields, ctx, "description"),
preview: opt_string(fields, ctx, "preview"),
})
} catch {
DecodeError::Msg(message) => Err(message)
}
}
///|
/// `LodyElicitationQuestion` — one question with its option set.
pub(all) struct LodyElicitationQuestion {
id : String?
question : String
header : String
options : Array[LodyElicitationOption]
multi_select : Bool
allow_custom_answer : Bool?
is_secret : Bool?
} derive(Eq, Debug)
///|
pub fn LodyElicitationQuestion::to_json(self : LodyElicitationQuestion) -> Json {
let pairs : Array[(String, Json)] = []
match self.id {
Some(v) => pairs.push(("id", Json::string(v)))
None => ()
}
pairs.push(("question", Json::string(self.question)))
pairs.push(("header", Json::string(self.header)))
pairs.push(
("options", Json::array(self.options.map(fn(option) { option.to_json() }))),
)
pairs.push(("multiSelect", Json::boolean(self.multi_select)))
match self.allow_custom_answer {
Some(v) => pairs.push(("allowCustomAnswer", Json::boolean(v)))
None => ()
}
match self.is_secret {
Some(v) => pairs.push(("isSecret", Json::boolean(v)))
None => ()
}
Json::object(Map::from_array(pairs))
}
///|
pub fn LodyElicitationQuestion::from_json(
raw : Json,
) -> Result[LodyElicitationQuestion, String] {
try {
let ctx = "lody.elicitation.question"
let fields = object_fields(raw, ctx)
let options : Array[LodyElicitationOption] = req_array(
fields, ctx, "options",
).map(item => {
match LodyElicitationOption::from_json(item) {
Ok(option) => option
Err(message) => raise DecodeError::Msg(message)
}
})
Ok(LodyElicitationQuestion::{
id: opt_string(fields, ctx, "id"),
question: req_string(fields, ctx, "question"),
header: req_string(fields, ctx, "header"),
options,
multi_select: req_bool(fields, ctx, "multiSelect"),
allow_custom_answer: opt_bool(fields, ctx, "allowCustomAnswer"),
is_secret: opt_bool(fields, ctx, "isSecret"),
})
} catch {
DecodeError::Msg(message) => Err(message)
}
}
///|
/// `LodyElicitationMeta` — the `_meta.lody.elicitation` value. Mounts at
/// request, property, and enum-option scope alike.
pub(all) struct LodyElicitationMeta {
version : Int
auto_resolve_after_seconds : Int64?
auto_resolve_at_epoch_seconds : Int64?
custom_answer_for : String?
secret : Bool?
preview : String?
questions : Array[LodyElicitationQuestion]?
answers : Map[String, LodyElicitationAnswer]?
} derive(Eq, Debug)
///|
pub fn LodyElicitationMeta::to_json(self : LodyElicitationMeta) -> Json {
let pairs : Array[(String, Json)] = [
("version", Json::number(self.version.to_double())),
]
match self.auto_resolve_after_seconds {
Some(v) =>
pairs.push(("autoResolveAfterSeconds", Json::number(v.to_double())))
None => ()
}
match self.auto_resolve_at_epoch_seconds {
Some(v) =>
pairs.push(("autoResolveAtEpochSeconds", Json::number(v.to_double())))
None => ()
}
match self.custom_answer_for {
Some(v) => pairs.push(("customAnswerFor", Json::string(v)))
None => ()
}
match self.secret {
Some(v) => pairs.push(("secret", Json::boolean(v)))
None => ()
}
match self.preview {
Some(v) => pairs.push(("preview", Json::string(v)))
None => ()
}
match self.questions {
Some(questions) =>
pairs.push(
(
"questions",
Json::array(questions.map(fn(question) { question.to_json() })),
),
)
None => ()
}
match self.answers {
Some(answers) => {
let answer_pairs : Array[(String, Json)] = []
for question_id, answer in answers {
answer_pairs.push((question_id, answer.to_json()))
}
pairs.push(("answers", Json::object(Map::from_array(answer_pairs))))
}
None => ()
}
Json::object(Map::from_array(pairs))
}
///|
pub fn LodyElicitationMeta::from_json(
raw : Json,
) -> Result[LodyElicitationMeta, String] {
try {
let ctx = "lody.elicitation"
let fields = object_fields(raw, ctx)
let version = req_int(fields, ctx, "version")
guard version == 1 else {
raise DecodeError::Msg(
"\{ctx}: unsupported version \{version} (expected 1)",
)
}
let questions : Array[LodyElicitationQuestion]? = match
fields.get("questions") {
None => None
Some(Json::Null) => None
Some(Json::Array(_)) =>
Some(
req_array(fields, ctx, "questions").map(item => {
match LodyElicitationQuestion::from_json(item) {
Ok(question) => question
Err(message) => raise DecodeError::Msg(message)
}
}),
)
Some(_) =>
raise DecodeError::Msg("\{ctx}: field \"questions\" must be an array")
}
let answers : Map[String, LodyElicitationAnswer]? = match
opt_object(fields, ctx, "answers") {
None => None
Some(answer_fields) => {
let parsed : Array[(String, LodyElicitationAnswer)] = []
for question_id, answer in answer_fields {
match LodyElicitationAnswer::from_json(answer) {
Ok(value) => parsed.push((question_id, value))
Err(message) => raise DecodeError::Msg(message)
}
}
Some(Map::from_array(parsed))
}
}
Ok(LodyElicitationMeta::{
version,
auto_resolve_after_seconds: opt_int64(
fields, ctx, "autoResolveAfterSeconds",
),
auto_resolve_at_epoch_seconds: opt_int64(
fields, ctx, "autoResolveAtEpochSeconds",
),
custom_answer_for: opt_string(fields, ctx, "customAnswerFor"),
secret: opt_bool(fields, ctx, "secret"),
preview: opt_string(fields, ctx, "preview"),
questions,
answers,
})
} catch {
DecodeError::Msg(message) => Err(message)
}
}