// A model described once, in moonmodel's vocabulary, then used twice here:
// to validate an inbound body and to write the OpenAPI document. The same
// description produces the SQL columns in moonorm and the SDL in moongql.
///|
fn address() -> @moonmodel.Model {
@moonmodel.Model::new("Address", [
@moonmodel.Field::new("city", Text),
@moonmodel.Field::new(
"country",
Text,
check=@moonmodel.Check::new(shortest=2, longest=2),
),
])
}
///|
fn order() -> @moonmodel.Model {
@moonmodel.Model::new(
"Order",
[
@moonmodel.Field::new(
"qty",
Int,
doc="how many",
check=@moonmodel.Check::new(least=1.0, most=99.0),
),
@moonmodel.Field::new(
"code",
Text,
check=@moonmodel.Check::new(shortest=3, longest=8, pattern="^[a-z]+$"),
),
@moonmodel.Field::new(
"colour",
Text,
check=@moonmodel.Check::new(options=[Json("red"), Json("blue")]),
),
@moonmodel.Field::new("ship_to", Ref("Address")),
@moonmodel.Field::new("note", Text, required=false, nullable=true),
],
doc="something somebody bought",
)
}
///|
async fn main {
// `known` is where a reference is looked up. Address is in it, so the
// schema is written out and the document ends up with a $ref to it.
let (schema, unresolved) = @moonapi.Schema::of_model(order(), known=[
address(),
])
for one in unresolved {
println("unresolved \{one.field} -> \{one.model}: \{one.why}")
}
let ep = @moonapi.Endpoint::new(request_body=schema)
let app = @moonapi.App::new()
app.post(
"/orders",
ctx => {
let errs = ep.validate(ctx)
if errs.length() > 0 {
@moonapi.unprocessable(errs)
} else {
@moonapi.text(201, "order accepted")
}
},
endpoint=ep,
summary="place an order",
)
let hit = (label : String, body : String) => {
let req : @moonasgi.Request = {
http_method: "POST",
path: "/orders",
query_string: b"",
headers: [("content-type", "application/json")],
body: @utf8.encode(body),
}
let r = app.handle(req)
println("\{label} -> \{r.status} \{@utf8.decode_lossy(r.body[:])}")
}
println("── validation, from the same description ──")
hit(
"within ", "{\"qty\":5,\"code\":\"abc\",\"colour\":\"red\",\"ship_to\":{\"city\":\"Wuhan\",\"country\":\"CN\"}}",
)
hit(
"outside ", "{\"qty\":0,\"code\":\"AB\",\"colour\":\"teal\",\"ship_to\":{\"city\":\"Wuhan\",\"country\":\"CHN\"}}",
)
// A nullable field may be null; one that is absent is a different matter,
// which is why the description keeps the two apart.
hit(
"null note", "{\"qty\":1,\"code\":\"abc\",\"colour\":\"blue\",\"ship_to\":{\"city\":\"Wuhan\",\"country\":\"CN\"},\"note\":null}",
)
println("")
println("── the document, from the same description ──")
println(app.openapi_json(version=OpenApi31))
// A reference nobody declared cannot be written: a name alone does not say
// what the thing looks like, so it is reported rather than guessed at.
println("")
println("── what cannot be written says so ──")
let (_, missing) = @moonapi.Schema::of_model(order())
for one in missing {
println("\{one.field} -> \{one.model}: \{one.why}")
}
}