// Middleware-style example for the moon_ua_parser library (T-10).
//
// Simulates a request-entry middleware: each incoming request carries a
// User-Agent header, which is parsed once and summarized per domain
// (browser / os / device) plus a bot verdict. The verdict follows uap-core
// semantics: catch-all spider rules report the device family "Spider", so
// `device.family == "Spider"` is the detection test.
//
// Only the public API is used (`@ua_parser.parse` with the optional
// `with_rule_index` flag); no library internals.
//
// Every UA string below is copied verbatim from this repo's own suites so
// the expected output is pre-documented:
// #1 Chrome desktop - tests/semantics (Chrome 120.0.0.0 / Windows 10 /
// device fallback "Other", rule indices 280 / 47 / none)
// #2 iPhone Safari - tests/differential (Mobile Safari 9.0 / iOS 9.3.1 /
// iPhone, Apple, iPhone); the '+'-encoded form is the URL-encoded
// shape in which this UA appears in real access logs
// #3 Googlebot - tests/semantics (Googlebot 2.1 / OS fallback "Other" /
// device Spider, Spider, Desktop)
//
// Run from the moon project root:
// moon run --target native examples/middleware
///|
/// One simulated request reaching the middleware entry point.
struct Request {
route : String
user_agent : String
}
///|
/// Join the available version segments with "." (missing segments are
/// skipped, matching uap-python's version join; "-" when none remain).
fn dotted(parts : Array[String?]) -> String {
let buf = StringBuilder()
let mut first = true
for part in parts {
match part {
Some(v) => {
if !first {
buf.write_string(".")
}
buf.write_string(v)
first = false
}
None => ()
}
}
let s = buf.to_string()
if s == "" {
"-"
} else {
s
}
}
///|
/// "Family major.minor" when a version exists, bare family otherwise.
fn family_with_version(family : String, parts : Array[String?]) -> String {
let v = dotted(parts)
if v == "-" {
family
} else {
"\{family} \{v}"
}
}
///|
/// Optional field rendered for the summary line ("-" instead of None).
fn opt(v : String?) -> String {
match v {
Some(s) => s
None => "-"
}
}
///|
/// Rule index rendered for the debug line ("none" when no rule matched).
fn idx(v : Int?) -> String {
match v {
Some(i) => "#\{i}"
None => "none"
}
}
///|
/// Handle one simulated request: parse and print the per-domain summary.
fn handle(id : Int, req : Request) -> Unit {
println("[request #\{id}] \{req.route}")
println(" User-Agent: \{req.user_agent}")
match @ua_parser.parse(req.user_agent) {
Ok(info) => {
let b = info.browser
println(
" Browser: \{family_with_version(b.family, [b.major, b.minor, b.patch, b.patch_minor])}",
)
let o = info.os
println(
" OS : \{family_with_version(o.family, [o.major, o.minor, o.patch, o.patch_minor])}",
)
let d = info.device
println(" Device : \{d.family} / \{opt(d.brand)} / \{opt(d.model)}")
// Bot detection per uap-core semantics: catch-all spider rules
// report the device family "Spider".
let is_spider = d.family == "Spider"
println(" Spider : \{is_spider}")
}
Err(_) => println(" parse error (unreachable: rules are precompiled)")
}
}
///|
/// Debug view for one request: which uap-core rule won per domain
/// (`parse` with `with_rule_index = true`; index 0-based, file order).
fn rule_debug(id : Int, req : Request) -> Unit {
match @ua_parser.parse(req.user_agent, with_rule_index=true) {
Ok(info) =>
println(
"[request #\{id}] rule-index debug: browser=\{idx(info.browser.rule_index)} os=\{idx(info.os.rule_index)} device=\{idx(info.device.rule_index)}",
)
Err(_) => println(" parse error (unreachable: rules are precompiled)")
}
}
///|
fn main {
let requests = [
Request::{
route: "GET /api/v1/users",
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
},
Request::{
route: "GET /feed",
user_agent: "Mozilla/5.0+(iPhone;+CPU+iPhone+OS+9_3_1+like+Mac+OS+X)+AppleWebKit/601.1.46+(KHTML,+like+Gecko)+Version/9.0+Mobile/13E238+Safari/601.1",
},
Request::{
route: "GET /robots.txt",
user_agent: "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
},
]
println("moon_ua_parser middleware demo - \{requests.length()} simulated requests")
for i, req in requests {
handle(i + 1, req)
}
rule_debug(1, requests[0])
}