microsoft-webui 3.0.2

Programmatic API for the WebUI build-time rendering framework
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

use rayon::prelude::*;
use std::borrow::Cow;
use webui_protocol::{CssStrategy, WebUIProtocol};

use super::graph::AssetGraphPlan;
use super::json::encode_json_string;
use crate::WebUIError;

const PARALLEL_RENDER_THRESHOLD: usize = 128;

pub(super) struct RenderedComponent<'a> {
    pub resource: Option<RenderedStyleResource<'a>>,
    pub template: Cow<'a, str>,
    pub functions: Option<&'a str>,
}

#[derive(Clone, Copy)]
pub(super) enum RenderedStyleResource<'a> {
    Link(&'a str),
    Style(&'a str),
    Module(&'a str),
}

pub(super) fn render_component_payloads<'a>(
    protocol: &'a WebUIProtocol,
    plan: &AssetGraphPlan,
) -> Result<Vec<Option<RenderedComponent<'a>>>, WebUIError> {
    let mut payloads: Vec<Option<RenderedComponent<'a>>> = std::iter::repeat_with(|| None)
        .take(plan.component_names.len())
        .collect();
    if plan.emitted_components.len() < PARALLEL_RENDER_THRESHOLD {
        for component in &plan.emitted_components {
            payloads[*component] = Some(render_component(
                protocol,
                plan.component_names[*component],
            )?);
        }
        return Ok(payloads);
    }

    let rendered: Vec<Result<(usize, RenderedComponent<'a>), WebUIError>> = plan
        .emitted_components
        .par_iter()
        .map(|component| {
            render_component(protocol, plan.component_names[*component])
                .map(|payload| (*component, payload))
        })
        .collect();
    for payload in rendered {
        let (component, payload) = payload?;
        payloads[component] = Some(payload);
    }
    Ok(payloads)
}

fn render_component<'a>(
    protocol: &'a WebUIProtocol,
    tag: &str,
) -> Result<RenderedComponent<'a>, WebUIError> {
    let component = protocol.components.get(tag).ok_or_else(|| {
        WebUIError::InvalidBuildOptions(format!(
            "component asset payload for <{tag}> is missing protocol metadata"
        ))
    })?;
    let resource = render_style_resource(protocol, tag)?;
    let template = if component.template_json.is_empty() {
        Cow::Owned(encode_json_string(
            &component.template,
            "component template",
        )?)
    } else {
        Cow::Borrowed(component.template_json.as_str())
    };
    let functions =
        (!component.template_functions.is_empty()).then_some(component.template_functions.as_str());
    Ok(RenderedComponent {
        resource,
        template,
        functions,
    })
}

/// Resolve a component's delivered style resource, tagged with the shape the
/// asset payload should emit.
///
/// The strategy-dependent field selection lives in
/// [`WebUIProtocol::component_style_resource`]; this only classifies the result
/// so the serializer knows which JSON shape to write.
pub(super) fn render_style_resource<'a>(
    protocol: &'a WebUIProtocol,
    tag: &str,
) -> Result<Option<RenderedStyleResource<'a>>, WebUIError> {
    if !protocol.components.contains_key(tag) {
        return Err(WebUIError::InvalidBuildOptions(format!(
            "component asset style resource for <{tag}> is missing protocol metadata"
        )));
    }
    let Some(resource) = protocol.component_style_resource(tag) else {
        return Ok(None);
    };
    Ok(Some(match protocol.css_strategy() {
        CssStrategy::Link => RenderedStyleResource::Link(resource),
        CssStrategy::Style => RenderedStyleResource::Style(resource),
        CssStrategy::Module => RenderedStyleResource::Module(resource),
    }))
}