microsoft-webui-desktop-build 3.0.2

Build-time typed IPC bindings for the WebUI desktop SDK
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

use std::collections::BTreeSet;
use std::fmt::Write;

use super::{message, short, HEADER};
use crate::{
    model::{Contract, Method},
    names::{pascal, rust_ident, snake},
    GenerateError,
};

pub(crate) fn emit(contract: &Contract, hash: &str) -> Result<String, GenerateError> {
    let mut out = String::from(HEADER);
    out.push_str(
        "pub mod messages { include!(\"ipc_messages.rs\"); }\nuse webui_desktop::ipc::*;\n",
    );
    writeln!(out, "pub const SCHEMA_HASH: &str = {hash:?};").ok();
    out.push_str("pub static MESSAGE_SHAPES: &[MessageShape] = &[\n");
    for msg in &contract.messages {
        out.push_str("MessageShape { fields: &[\n");
        let oneofs: BTreeSet<_> = msg
            .fields
            .iter()
            .filter_map(|f| f.oneof.as_deref())
            .collect();
        for field in &msg.fields {
            let kind = if field.kind == "message" {
                let (index, _) = message(contract, field.target.as_deref().unwrap_or(""))?;
                format!("FieldKind::Message({index})")
            } else {
                format!("FieldKind::{}", pascal(&field.kind))
            };
            let oneof = field
                .oneof
                .as_deref()
                .and_then(|name| oneofs.iter().position(|n| *n == name));
            writeln!(out, "FieldShape {{ number: {}, kind: {kind}, repeated: {}, packed: {}, oneof: {oneof:?}, map_key: {} }},", field.number, field.repeated, field.packed, field.map_key).ok();
        }
        out.push_str("] },\n");
    }
    out.push_str("];\n");
    for (i, _) in contract.messages.iter().enumerate() {
        writeln!(out, "fn validate_{i}(bytes: &[u8], limits: &IpcLimits) -> Result<(), IpcError> {{ validate_message(bytes, {i}, MESSAGE_SHAPES, limits) }}").ok();
    }
    writeln!(out, "pub static SCHEMA: IpcSchema = IpcSchema {{ name: {:?}, major: {}, hash: SCHEMA_HASH, methods: &[", contract.name, contract.major).ok();
    for method in &contract.methods {
        let (input, _) = message(contract, &method.input)?;
        let response = if method.kind == "rpc" {
            format!("Some(validate_{})", message(contract, &method.output)?.0)
        } else {
            "None".into()
        };
        writeln!(out, "MethodDescriptor {{ id: {}, name: {:?}, receiver: Endpoint::{}, kind: MethodKind::{}, development_only: {}, validate_request: validate_{input}, validate_response: {response} }},", method.id, method.name, pascal(&method.receiver), pascal(&method.kind), method.development_only).ok();
    }
    out.push_str("] };\n");
    let services: BTreeSet<_> = contract
        .methods
        .iter()
        .map(|m| m.service.as_str())
        .collect();
    for service in services {
        let methods: Vec<_> = contract
            .methods
            .iter()
            .filter(|m| m.service == service)
            .collect();
        emit_service(&mut out, contract, service, &methods)?;
    }
    Ok(out)
}

fn emit_service(
    out: &mut String,
    contract: &Contract,
    service: &str,
    methods: &[&Method],
) -> Result<(), GenerateError> {
    let module = rust_ident(&snake(service));
    writeln!(out, "pub mod {module} {{ use super::*;").ok();
    for method in methods {
        let marker = rust_ident(&pascal(short(&method.name)));
        let request = &message(contract, &method.input)?.1.rust;
        let receiver = pascal(&method.receiver);
        writeln!(out, "#[derive(Debug, Clone, Copy)] pub struct {marker};").ok();
        if method.kind == "rpc" {
            let response = &message(contract, &method.output)?.1.rust;
            writeln!(out, "impl Rpc for {marker} {{ type Request = {request}; type Response = {response}; type Receiver = {receiver}; const ID: u32 = {}; }}", method.id).ok();
        } else {
            writeln!(out, "impl Event for {marker} {{ type Payload = {request}; type Receiver = {receiver}; const ID: u32 = {}; }}", method.id).ok();
            if method.receiver == "host" {
                let name = snake(short(&method.name));
                writeln!(out, "pub fn subscribe_{name}<F, Fut>(session: &IpcSession, callback: F) -> Result<IpcSubscription, IpcError> where F: Fn(NotificationContext, {request}) -> Fut + Send + Sync + 'static, Fut: std::future::Future<Output = Result<(), IpcError>> + Send + 'static {{ session.subscribe::<{marker}, F, Fut>(callback) }}").ok();
            }
        }
    }
    out.push_str("}\n");
    if methods.first().is_some_and(|m| m.receiver == "host") {
        emit_host(out, contract, service, methods)
    } else {
        emit_client(out, contract, service, methods)
    }
}

fn emit_host(
    out: &mut String,
    contract: &Contract,
    service: &str,
    methods: &[&Method],
) -> Result<(), GenerateError> {
    let handler = format!("{}Handler", pascal(service));
    writeln!(out, "pub trait {handler}: Send + Sync + 'static {{").ok();
    for method in methods {
        let name = rust_ident(&snake(short(&method.name)));
        let request = &message(contract, &method.input)?.1.rust;
        let (context, response) = if method.kind == "rpc" {
            (
                "RequestContext",
                message(contract, &method.output)?.1.rust.as_str(),
            )
        } else {
            ("NotificationContext", "()")
        };
        writeln!(
            out,
            "fn {name}(&self, context: {context}, request: {request}) -> IpcFuture<{response}>;"
        )
        .ok();
    }
    out.push_str("}\n");
    writeln!(out, "pub fn register_{}(registry: &mut IpcRegistry, handler: std::sync::Arc<dyn {handler}>) -> Result<(), IpcError> {{", snake(service)).ok();
    out.push_str("registry.validate_registration(&[");
    for method in methods {
        write!(out, "{},", method.id).ok();
    }
    out.push_str("])?;\n");
    for method in methods {
        let name = rust_ident(&snake(short(&method.name)));
        let register = if method.kind == "rpc" {
            "register"
        } else {
            "register_notification"
        };
        writeln!(out, "{{ let handler = std::sync::Arc::clone(&handler); registry.{register}::<{}::{}, _, _>(move |context, request| handler.{name}(context, request))?; }}", rust_ident(&snake(service)), rust_ident(&pascal(short(&method.name)))).ok();
    }
    out.push_str("Ok(()) }\n");
    Ok(())
}

fn emit_client(
    out: &mut String,
    contract: &Contract,
    service: &str,
    methods: &[&Method],
) -> Result<(), GenerateError> {
    let client = format!("{}Client", pascal(service));
    writeln!(out, "#[derive(Clone)] pub struct {client} {{ session: IpcSession }}\nimpl {client} {{ pub fn new(session: IpcSession) -> Self {{ Self {{ session }} }}").ok();
    for method in methods {
        let name = rust_ident(&snake(short(&method.name)));
        let marker = format!(
            "{}::{}",
            rust_ident(&snake(service)),
            rust_ident(&pascal(short(&method.name)))
        );
        let request = &message(contract, &method.input)?.1.rust;
        if method.kind == "rpc" {
            let response = &message(contract, &method.output)?.1.rust;
            writeln!(out, "pub fn {name}(&self, request: {request}, options: CallOptions) -> IpcCall<{response}> {{ self.session.call::<{marker}>(request, options) }}").ok();
        } else {
            writeln!(out, "pub fn {name}(&self, payload: {request}) -> IpcFuture<()> {{ self.session.notify::<{marker}>(payload) }}").ok();
        }
    }
    out.push_str("}\n");
    Ok(())
}