///|
/// `mctl gateway`: a `.proto` reachable over HTTP. Routes come from a mapping file
/// when there is one and from the gRPC paths themselves when there is not, and the
/// transport behind them is a single port, so the generated file compiles with no
/// gRPC dependency at all.
///
/// The proto below has the three cases a gateway has to answer for: a plain RPC, a
/// second one on another service, and a streaming RPC that cannot be a route.
///
/// moon run src/examples/16-gateway
#coverage.skip
fn main {
let proto = @moonctl.parse_proto(
(
#|syntax = "proto3";
#|package shop;
#|
#|message CreateReq { string name = 1; }
#|message User { string id = 1; }
#|
#|service UserService {
#| rpc Create(CreateReq) returns (User);
#| rpc Watch(CreateReq) returns (stream User);
#|}
#|
#|service StockService {
#| rpc Count(CreateReq) returns (User);
#|}
),
)
// the default mapping: the gRPC path, with the package dropped
println("---- default routes")
for r in @moonctl.default_routes(proto) {
println(" " + r.verb + " " + r.path + " -> " + r.rpc)
}
println("")
// and the mapping a project writes once the URLs are decided
println("---- mapped routes")
let (routes, skipped) = @moonctl.parse_route_map(
proto,
(
#|# what the browser sees
#|post /v1/users /shop.UserService/Create
#|get /v1/stock /shop.StockService/Count
#|post /v1/ghost /shop.UserService/Ghost
),
)
for r in routes {
println(" " + r.verb + " " + r.path + " -> " + r.rpc)
}
println(
" skipped " +
skipped.length().to_string() +
" line(s) naming no such method",
)
println("")
let gw = @moonctl.generate_gateway(proto, routes)
println("gateway: " + gw.length().to_string() + " bytes")
println("file: " + @moonctl.gateway_file_name("proto/shop.proto"))
println("")
for
anchor in [
"pub fn routes() -> Array[GatewayRoute] {", "pub fn Gateway::handle(", "pub fn Gateway::handle_text(",
] {
println("---- " + anchor)
println(excerpt(gw, anchor, 10))
}
// the streaming RPC is not a route, and the file says which one was left out
println("---- streaming")
println(
" Watch is called out: " +
(at_of(gw, "/shop.UserService/Watch") >= 0).to_string(),
)
}
///|
/// The first index in `hay` where `needle` starts, or `-1`.
fn at_of(hay : String, needle : String) -> Int32 {
let hn = hay.length()
let nn = needle.length()
if nn == 0 {
return 0
}
for i = 0; i + nn <= hn; i = i + 1 {
let mut same = true
for j = 0; j < nn; j = j + 1 {
if hay[i + j] != needle[j] {
same = false
break
}
}
if same {
return i
}
}
-1
}
///|
/// The `lines` lines of `text` that follow the first occurrence of `anchor`.
fn excerpt(text : String, anchor : String, lines : Int32) -> String {
let at = at_of(text, anchor)
if at < 0 {
return anchor + ": not found"
}
let mut end = at
let mut seen = 0
while end < text.length() && seen < lines {
if text[end] == '\n' {
seen = seen + 1
}
end = end + 1
}
text[at:end].to_owned()
}