webpage_quality_analyzer 1.0.2

High-performance webpage quality analyzer with 115 comprehensive metrics - Rust library with WASM, C++, and Python bindings
Documentation
/**
 * @file output_customization.rs
 * @brief FFI bindings for field selector and output customization (Phase B)
 */

use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;

use crate::utils::json_optimizer::{FieldSelector, FieldSelectorBuilder, OptimizedSerializer, SerializationOptions};
use super::types::{CFieldSelector, CReport};
use super::conversions::{c_str_to_rust, cast_report};
use super::errors::CErrorCode;

// Internal wrapper tracking fields/sections
pub(crate) struct FieldSelectorInternal {
    pub include_fields: Vec<String>,
    pub exclude_fields: Vec<String>,
    pub include_sections: Vec<String>,
    pub exclude_sections: Vec<String>,
}

impl FieldSelectorInternal {
    fn new() -> Self {
        Self {
            include_fields: Vec::new(),
            exclude_fields: Vec::new(),
            include_sections: Vec::new(),
            exclude_sections: Vec::new(),
        }
    }
    
    fn to_field_selector(&self) -> FieldSelector {
        let mut builder = FieldSelector::builder();
        for field in &self.include_fields {
            builder = builder.include_field(field.clone());
        }
        for field in &self.exclude_fields {
            builder = builder.exclude_field(field.clone());
        }
        for section in &self.include_sections {
            builder = builder.include_section(section.clone());
        }
        for section in &self.exclude_sections {
            builder = builder.exclude_section(section.clone());
        }
        builder.build()
    }
}

#[no_mangle]
pub extern "C" fn wqa_field_selector_new() -> *mut CFieldSelector {
    let internal = Box::new(FieldSelectorInternal::new());
    Box::into_raw(internal) as *mut CFieldSelector
}

#[no_mangle]
pub extern "C" fn wqa_field_selector_free(selector: *mut CFieldSelector) {
    if selector.is_null() {
        return;
    }
    unsafe {
        let _ = Box::from_raw(selector as *mut FieldSelectorInternal);
    }
}

#[no_mangle]
pub extern "C" fn wqa_field_selector_include_field(
    selector: *mut CFieldSelector,
    field: *const c_char,
) -> CErrorCode {
    if selector.is_null() || field.is_null() {
        return CErrorCode::InvalidPointer;
    }
    let field_str = match c_str_to_rust(field) {
        Ok(s) => s,
        Err(_) => return CErrorCode::Utf8Error,
    };
    unsafe {
        let internal = &mut *(selector as *mut FieldSelectorInternal);
        internal.include_fields.push(field_str);
    }
    CErrorCode::Success
}

#[no_mangle]
pub extern "C" fn wqa_field_selector_exclude_field(
    selector: *mut CFieldSelector,
    field: *const c_char,
) -> CErrorCode {
    if selector.is_null() || field.is_null() {
        return CErrorCode::InvalidPointer;
    }
    let field_str = match c_str_to_rust(field) {
        Ok(s) => s,
        Err(_) => return CErrorCode::Utf8Error,
    };
    unsafe {
        let internal = &mut *(selector as *mut FieldSelectorInternal);
        internal.exclude_fields.push(field_str);
    }
    CErrorCode::Success
}

#[no_mangle]
pub extern "C" fn wqa_field_selector_include_section(
    selector: *mut CFieldSelector,
    section: *const c_char,
) -> CErrorCode {
    if selector.is_null() || section.is_null() {
        return CErrorCode::InvalidPointer;
    }
    let section_str = match c_str_to_rust(section) {
        Ok(s) => s,
        Err(_) => return CErrorCode::Utf8Error,
    };
    unsafe {
        let internal = &mut *(selector as *mut FieldSelectorInternal);
        internal.include_sections.push(section_str);
    }
    CErrorCode::Success
}

#[no_mangle]
pub extern "C" fn wqa_field_selector_exclude_section(
    selector: *mut CFieldSelector,
    section: *const c_char,
) -> CErrorCode {
    if selector.is_null() || section.is_null() {
        return CErrorCode::InvalidPointer;
    }
    let section_str = match c_str_to_rust(section) {
        Ok(s) => s,
        Err(_) => return CErrorCode::Utf8Error,
    };
    unsafe {
        let internal = &mut *(selector as *mut FieldSelectorInternal);
        internal.exclude_sections.push(section_str);
    }
    CErrorCode::Success
}

#[no_mangle]
pub extern "C" fn wqa_report_to_json_with_selector(
    report: *const CReport,
    selector: *const CFieldSelector,
    compact: bool,
) -> *mut c_char {
    if report.is_null() {
        return ptr::null_mut();
    }

    let report_internal = match unsafe { cast_report(report) } {
        Some(r) => r,
        None => return ptr::null_mut(),
    };

    let json_result = if selector.is_null() {
        if compact {
            serde_json::to_string(&report_internal.report)
        } else {
            serde_json::to_string_pretty(&report_internal.report)
        }
    } else {
        let selector_internal = unsafe { &*(selector as *const FieldSelectorInternal) };
        let field_selector = selector_internal.to_field_selector();
        let opts = SerializationOptions {
            compact,
            skip_empty_fields: true,
            minimal_output: false,
            streaming: false,
            buffer_size: 8192,
        };
        OptimizedSerializer::serialize_with_selector(
            &report_internal.report,
            &field_selector,
            Some(&opts)
        )
    };

    match json_result {
        Ok(json) => match CString::new(json) {
            Ok(c_string) => c_string.into_raw(),
            Err(_) => ptr::null_mut(),
        },
        Err(_) => ptr::null_mut(),
    }
}

#[no_mangle]
pub extern "C" fn wqa_report_to_ultra_compact_json(
    report: *const CReport,
) -> *mut c_char {
    if report.is_null() {
        return ptr::null_mut();
    }

    let report_internal = match unsafe { cast_report(report) } {
        Some(r) => r,
        None => return ptr::null_mut(),
    };

    let selector = FieldSelector::builder()
        .include_fields(["url", "score", "verdict", "version"])
        .include_field("metrics.html_analysis.content.word_count")
        .include_field("metrics.html_analysis.seo.title_length")
        .include_field("metrics.html_analysis.technical.html_bytes")
        .build();

    let opts = SerializationOptions {
        compact: true,
        skip_empty_fields: true,
        minimal_output: true,
        streaming: false,
        buffer_size: 4096,
    };

    match OptimizedSerializer::serialize_with_selector(&report_internal.report, &selector, Some(&opts)) {
        Ok(json) => match CString::new(json) {
            Ok(c_string) => c_string.into_raw(),
            Err(_) => ptr::null_mut(),
        },
        Err(_) => ptr::null_mut(),
    }
}