// Manifest Adapter for Sol
//
// Converts between SolRoutes and RouteManifest
//
// Two-way conversion:
// - SolRoutes → RouteManifest (for client manifest generation)
// - RouteManifest → SolRoutes (for handler injection)
// =============================================================================
// SolRoutes to RouteManifest
// =============================================================================
///|
/// Convert SolRoutes array to RouteManifest
pub fn sol_routes_to_manifest(
routes : Array[SolRoutes],
base? : String = "",
) -> @sol_routes.RouteManifest {
let manifest_routes : Array[@sol_routes.RouteEntry] = []
collect_routes(routes, base, manifest_routes)
@sol_routes.RouteManifest::{
routes: manifest_routes,
fallback: @sol_routes.FallbackConfig::NotFound(path="/404.html"),
}
}
///|
/// Recursively collect routes from SolRoutes tree
fn collect_routes(
routes : Array[SolRoutes],
base : String,
result : Array[@sol_routes.RouteEntry],
) -> Unit {
for route in routes {
match route {
Page(path~, handler~, title~, meta~, revalidate~, cache~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let catch_all = extract_catch_all_info(full_path)
// Determine render mode from revalidate
let mode = match revalidate {
Some(seconds) => @sol_routes.RenderMode::Isr(revalidate=seconds)
None => @sol_routes.RenderMode::Ssr
}
// Create dynamic route entry
let entry = @sol_routes.RouteEntry::Dynamic(@sol_routes.DynamicRouteEntry::{
path: full_path,
pattern,
param_names,
source: full_path, // For Sol, source is the path
mode,
layout: None,
catch_all,
title: Some(title),
islands: [],
})
result.push(entry)
// Suppress unused warnings
let _ = handler
let _ = meta
let _ = cache
}
Layout(segment~, layout~, children~) => {
// Recurse into children with updated base path
let new_base = manifest_normalize_path(base + segment)
collect_routes(children, new_base, result)
let _ = layout
}
Get(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Get,
source: full_path,
})
result.push(entry)
let _ = handler
}
Post(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Post,
source: full_path,
})
result.push(entry)
let _ = handler
}
Put(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Put,
source: full_path,
})
result.push(entry)
let _ = handler
}
Delete(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Delete,
source: full_path,
})
result.push(entry)
let _ = handler
}
Patch(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Patch,
source: full_path,
})
result.push(entry)
let _ = handler
}
RawGet(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Get,
source: full_path,
})
result.push(entry)
let _ = handler
}
RawPost(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Post,
source: full_path,
})
result.push(entry)
let _ = handler
}
RawPut(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Put,
source: full_path,
})
result.push(entry)
let _ = handler
}
RawDelete(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Delete,
source: full_path,
})
result.push(entry)
let _ = handler
}
RawPatch(path~, handler~) => {
let full_path = manifest_normalize_path(base + path)
let (pattern, param_names) = extract_route_pattern(full_path)
let entry = @sol_routes.RouteEntry::Api(@sol_routes.ApiRouteEntry::{
path: full_path,
pattern,
param_names,
http_method: @sol_routes.HttpMethod::Patch,
source: full_path,
})
result.push(entry)
let _ = handler
}
WithMiddleware(middleware~, children~) => {
// Middleware doesn't affect manifest, just recurse
collect_routes(children, base, result)
let _ = middleware
}
WithAssets(styles~, scripts~, children~) => {
// Assets don't affect the route manifest, just recurse.
collect_routes(children, base, result)
let _ = (styles, scripts)
}
}
}
}
// =============================================================================
// Helper Functions (delegating to core/routes/pattern_utils)
// =============================================================================
///|
/// Normalize path for manifest (remove double slashes, ensure leading slash)
fn manifest_normalize_path(path : String) -> String {
@sol_routes.normalize_url_path(path)
}
///|
/// Extract regex pattern and parameter names from path
fn extract_route_pattern(path : String) -> (String, Array[String]) {
@sol_routes.extract_url_pattern(path)
}
///|
/// Extract catch-all info from path
fn extract_catch_all_info(path : String) -> @sol_routes.CatchAllInfo? {
@sol_routes.extract_catch_all(path)
}
// =============================================================================
// RouteManifest to SolRoutes (Handler Injection)
// =============================================================================
///|
/// Handler factory type - creates a PageHandler from a route entry
/// Returns None if this factory cannot handle the entry
pub(all) struct PageHandlerFactory((@sol_routes.RouteEntry) -> PageHandler?)
///|
/// Handler factory type - creates an ApiHandler from a route entry
/// Returns None if this factory cannot handle the entry
pub(all) struct ApiHandlerFactory((@sol_routes.RouteEntry) -> ApiHandler?)
///|
/// Convert RouteManifest to SolRoutes with handler injection
///
/// This allows external modules (like Sol SSG) to inject content processing
/// by providing factory functions that create handlers from route metadata.
///
/// Example usage:
/// ```
/// let manifest = scan_pages_dir(fs, "docs", cwd)
/// let routes = manifest_to_sol_routes(manifest, my_page_factory, my_api_factory)
/// register_sol_routes(app, routes, config)
/// ```
pub fn manifest_to_sol_routes(
manifest : @sol_routes.RouteManifest,
page_factory : PageHandlerFactory,
api_factory : ApiHandlerFactory,
) -> Array[SolRoutes] {
let routes : Array[SolRoutes] = []
for entry in manifest.routes {
match convert_entry_to_sol_route(entry, page_factory, api_factory) {
Some(sol_route) => routes.push(sol_route)
None => () // Skip entries that factories cannot handle
}
}
routes
}
///|
/// Convert a single RouteEntry to SolRoutes
fn convert_entry_to_sol_route(
entry : @sol_routes.RouteEntry,
page_factory : PageHandlerFactory,
api_factory : ApiHandlerFactory,
) -> SolRoutes? {
match entry {
@sol_routes.Static(e) => convert_static_entry(e, page_factory)
@sol_routes.Dynamic(e) => convert_dynamic_entry(e, page_factory)
@sol_routes.Api(e) => convert_api_entry(e, api_factory)
@sol_routes.Component(e) => convert_component_entry(e, page_factory)
}
}
///|
/// Convert Static route entry to SolRoutes::Page
fn convert_static_entry(
entry : @sol_routes.StaticRouteEntry,
page_factory : PageHandlerFactory,
) -> SolRoutes? {
let route_entry = @sol_routes.RouteEntry::Static(entry)
match (page_factory.0)(route_entry) {
Some(handler) =>
Some(
SolRoutes::Page(
path=entry.path,
handler~,
title=entry.title.unwrap_or(""),
meta=[],
revalidate=None,
cache=None,
),
)
None => None
}
}
///|
/// Convert Dynamic route entry to SolRoutes::Page
fn convert_dynamic_entry(
entry : @sol_routes.DynamicRouteEntry,
page_factory : PageHandlerFactory,
) -> SolRoutes? {
let route_entry = @sol_routes.RouteEntry::Dynamic(entry)
match (page_factory.0)(route_entry) {
Some(handler) => {
// Extract revalidate from render mode
let revalidate = match entry.mode {
@sol_routes.RenderMode::Isr(revalidate~) => Some(revalidate)
_ => None
}
Some(
SolRoutes::Page(
path=entry.path,
handler~,
title=entry.title.unwrap_or(""),
meta=[],
revalidate~,
cache=None,
),
)
}
None => None
}
}
///|
/// Convert API route entry to SolRoutes::Get or SolRoutes::Post
fn convert_api_entry(
entry : @sol_routes.ApiRouteEntry,
api_factory : ApiHandlerFactory,
) -> SolRoutes? {
let route_entry = @sol_routes.RouteEntry::Api(entry)
match (api_factory.0)(route_entry) {
Some(handler) =>
match entry.http_method {
@sol_routes.HttpMethod::Get =>
Some(SolRoutes::Get(path=entry.path, handler~))
@sol_routes.HttpMethod::Post =>
Some(SolRoutes::Post(path=entry.path, handler~))
@sol_routes.HttpMethod::Put =>
Some(SolRoutes::Post(path=entry.path, handler~)) // Use POST for PUT
@sol_routes.HttpMethod::Delete =>
Some(SolRoutes::Post(path=entry.path, handler~)) // Use POST for DELETE
@sol_routes.HttpMethod::Patch =>
Some(SolRoutes::Post(path=entry.path, handler~)) // Use POST for PATCH
@sol_routes.HttpMethod::All =>
Some(SolRoutes::Get(path=entry.path, handler~)) // Use GET for ALL
}
None => None
}
}
///|
/// Convert Component route entry to SolRoutes::Page
fn convert_component_entry(
entry : @sol_routes.ComponentRouteEntry,
page_factory : PageHandlerFactory,
) -> SolRoutes? {
let route_entry = @sol_routes.RouteEntry::Component(entry)
match (page_factory.0)(route_entry) {
Some(handler) => {
// Extract revalidate from render mode
let revalidate = match entry.mode {
@sol_routes.RenderMode::Isr(revalidate~) => Some(revalidate)
_ => None
}
Some(
SolRoutes::Page(
path=entry.path,
handler~,
title="",
meta=[],
revalidate~,
cache=None,
),
)
}
None => None
}
}
// =============================================================================
// Factory Combinators
// =============================================================================
///|
/// Combine multiple page factories into one
/// Tries each factory in order until one returns a handler
pub fn combine_page_factories(
factories : Array[PageHandlerFactory],
) -> PageHandlerFactory {
PageHandlerFactory(fn(entry) {
for factory in factories {
match (factory.0)(entry) {
Some(handler) => return Some(handler)
None => continue
}
}
None
})
}
///|
/// Combine multiple API factories into one
/// Tries each factory in order until one returns a handler
pub fn combine_api_factories(
factories : Array[ApiHandlerFactory],
) -> ApiHandlerFactory {
ApiHandlerFactory(fn(entry) {
for factory in factories {
match (factory.0)(entry) {
Some(handler) => return Some(handler)
None => continue
}
}
None
})
}
///|
/// Create a page factory that always returns None (fallback/null factory)
pub fn null_page_factory() -> PageHandlerFactory {
PageHandlerFactory(fn(_entry) { None })
}
///|
/// Create an API factory that always returns None (fallback/null factory)
pub fn null_api_factory() -> ApiHandlerFactory {
ApiHandlerFactory(fn(_entry) { None })
}