Skip to main content

ri/java/classes/
validation.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Validation Module JNI Bindings
19//!
20//! JNI bindings for Ri validation classes.
21//!
22//! ## Security
23//!
24//! This module uses a secure pointer registry to prevent:
25//! - Use-After-Free: Pointers are validated before access
26//! - Double-Free: Free operations are idempotent
27//! - Invalid Pointer Dereference: All pointers are tracked
28
29use jni::JNIEnv;
30use jni::objects::{JClass, JString};
31use jni::sys::{jlong, jboolean, jint, jstring};
32use dashmap::DashSet;
33
34use crate::validation::{
35    RiValidationError, RiValidationResult, RiValidationSeverity,
36    RiSanitizer, RiSanitizationConfig, RiSchemaValidator,
37    RiValidatorBuilder, RiValidationRunner,
38};
39
40lazy_static::lazy_static! {
41    static ref VALIDATION_MODULE_REGISTRY: DashSet<usize> = DashSet::new();
42    static ref VALIDATION_ERROR_REGISTRY: DashSet<usize> = DashSet::new();
43    static ref VALIDATION_RESULT_REGISTRY: DashSet<usize> = DashSet::new();
44    static ref SANITIZATION_CONFIG_REGISTRY: DashSet<usize> = DashSet::new();
45    static ref SANITIZER_REGISTRY: DashSet<usize> = DashSet::new();
46    static ref SCHEMA_VALIDATOR_REGISTRY: DashSet<usize> = DashSet::new();
47    static ref VALIDATION_RUNNER_REGISTRY: DashSet<usize> = DashSet::new();
48    static ref VALIDATOR_BUILDER_REGISTRY: DashSet<usize> = DashSet::new();
49}
50
51fn register_ptr(registry: &DashSet<usize>, ptr: usize) {
52    registry.insert(ptr);
53}
54
55fn unregister_ptr(registry: &DashSet<usize>, ptr: usize) -> bool {
56    registry.remove(&ptr).is_some()
57}
58
59fn is_valid_ptr(registry: &DashSet<usize>, ptr: usize) -> bool {
60    ptr != 0 && registry.contains(&ptr)
61}
62
63// =============================================================================
64// RiValidationModule JNI Bindings (static validation helpers)
65// =============================================================================
66
67fn box_result(result: RiValidationResult) -> jlong {
68    let result = Box::new(result);
69    let result_ptr = Box::into_raw(result) as jlong;
70    register_ptr(&VALIDATION_RESULT_REGISTRY, result_ptr as usize);
71    result_ptr
72}
73
74fn get_string_arg(env: &mut JNIEnv, s: &JString, arg: &str) -> Option<String> {
75    match env.get_string(s) {
76        Ok(v) => Some(v.into()),
77        Err(_) => {
78            let _ = env.throw_new(
79                "java/lang/IllegalArgumentException",
80                &format!("Failed to get {} string", arg),
81            );
82            None
83        }
84    }
85}
86
87#[no_mangle]
88pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationModule_nativeValidateEmail(
89    mut env: JNIEnv,
90    _class: JClass,
91    email: JString,
92) -> jlong {
93    let Some(email) = get_string_arg(&mut env, &email, "email") else { return 0 };
94    let result = RiValidatorBuilder::new("email")
95        .is_email()
96        .max_length(255)
97        .build()
98        .validate_value(Some(&email));
99    box_result(result)
100}
101
102#[no_mangle]
103pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationModule_nativeValidateUsername(
104    mut env: JNIEnv,
105    _class: JClass,
106    username: JString,
107) -> jlong {
108    let Some(username) = get_string_arg(&mut env, &username, "username") else { return 0 };
109    let result = RiValidatorBuilder::new("username")
110        .not_empty()
111        .min_length(3)
112        .max_length(32)
113        .alphanumeric()
114        .build()
115        .validate_value(Some(&username));
116    box_result(result)
117}
118
119#[no_mangle]
120pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationModule_nativeValidatePassword(
121    mut env: JNIEnv,
122    _class: JClass,
123    password: JString,
124) -> jlong {
125    let Some(password) = get_string_arg(&mut env, &password, "password") else { return 0 };
126    let result = RiValidatorBuilder::new("password")
127        .not_empty()
128        .min_length(8)
129        .build()
130        .validate_value(Some(&password));
131    box_result(result)
132}
133
134#[no_mangle]
135pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationModule_nativeValidateUrl(
136    mut env: JNIEnv,
137    _class: JClass,
138    url: JString,
139) -> jlong {
140    let Some(url) = get_string_arg(&mut env, &url, "url") else { return 0 };
141    let result = RiValidatorBuilder::new("url")
142        .is_url()
143        .build()
144        .validate_value(Some(&url));
145    box_result(result)
146}
147
148#[no_mangle]
149pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationModule_nativeValidateIp(
150    mut env: JNIEnv,
151    _class: JClass,
152    ip: JString,
153) -> jlong {
154    let Some(ip) = get_string_arg(&mut env, &ip, "ip") else { return 0 };
155    let result = RiValidatorBuilder::new("ip")
156        .is_ip()
157        .build()
158        .validate_value(Some(&ip));
159    box_result(result)
160}
161
162// =============================================================================
163// RiValidationError JNI Bindings
164// =============================================================================
165
166#[no_mangle]
167pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationError_getField0<'local>(
168    mut env: JNIEnv<'local>,
169    _class: JClass<'local>,
170    ptr: jlong,
171) -> jstring {
172    if !is_valid_ptr(&VALIDATION_ERROR_REGISTRY, ptr as usize) {
173        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidationError pointer")
174            .unwrap_or(());
175        return std::ptr::null_mut();
176    }
177
178    let error = unsafe { &*(ptr as *const RiValidationError) };
179    match env.new_string(&error.field) {
180        Ok(s) => s.into_raw(),
181        Err(_) => std::ptr::null_mut(),
182    }
183}
184
185#[no_mangle]
186pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationError_getMessage0<'local>(
187    mut env: JNIEnv<'local>,
188    _class: JClass<'local>,
189    ptr: jlong,
190) -> jstring {
191    if !is_valid_ptr(&VALIDATION_ERROR_REGISTRY, ptr as usize) {
192        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidationError pointer")
193            .unwrap_or(());
194        return std::ptr::null_mut();
195    }
196
197    let error = unsafe { &*(ptr as *const RiValidationError) };
198    match env.new_string(&error.message) {
199        Ok(s) => s.into_raw(),
200        Err(_) => std::ptr::null_mut(),
201    }
202}
203
204#[no_mangle]
205pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationError_getCode0<'local>(
206    mut env: JNIEnv<'local>,
207    _class: JClass<'local>,
208    ptr: jlong,
209) -> jstring {
210    if !is_valid_ptr(&VALIDATION_ERROR_REGISTRY, ptr as usize) {
211        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidationError pointer")
212            .unwrap_or(());
213        return std::ptr::null_mut();
214    }
215
216    let error = unsafe { &*(ptr as *const RiValidationError) };
217    match env.new_string(&error.code) {
218        Ok(s) => s.into_raw(),
219        Err(_) => std::ptr::null_mut(),
220    }
221}
222
223#[no_mangle]
224pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationError_getSeverity0(
225    mut env: JNIEnv,
226    _class: JClass,
227    ptr: jlong,
228) -> jint {
229    if !is_valid_ptr(&VALIDATION_ERROR_REGISTRY, ptr as usize) {
230        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidationError pointer")
231            .unwrap_or(());
232        return 0;
233    }
234
235    let error = unsafe { &*(ptr as *const RiValidationError) };
236    match error.severity.clone() {
237        RiValidationSeverity::Error => 0,
238        RiValidationSeverity::Warning => 1,
239        RiValidationSeverity::Info => 2,
240        RiValidationSeverity::Critical => 3,
241    }
242}
243
244#[no_mangle]
245pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationError_free0(
246    _env: JNIEnv,
247    _class: JClass,
248    ptr: jlong,
249) {
250    if unregister_ptr(&VALIDATION_ERROR_REGISTRY, ptr as usize) {
251        unsafe {
252            let _ = Box::from_raw(ptr as *mut RiValidationError);
253        }
254    }
255}
256
257// =============================================================================
258// RiValidationResult JNI Bindings
259// =============================================================================
260
261#[no_mangle]
262pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationResult_isValid0(
263    mut env: JNIEnv,
264    _class: JClass,
265    ptr: jlong,
266) -> jboolean {
267    if !is_valid_ptr(&VALIDATION_RESULT_REGISTRY, ptr as usize) {
268        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidationResult pointer")
269            .unwrap_or(());
270        return 0;
271    }
272
273    let result = unsafe { &*(ptr as *const RiValidationResult) };
274    result.is_valid as jboolean
275}
276
277#[no_mangle]
278pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationResult_free0(
279    _env: JNIEnv,
280    _class: JClass,
281    ptr: jlong,
282) {
283    if unregister_ptr(&VALIDATION_RESULT_REGISTRY, ptr as usize) {
284        unsafe {
285            let _ = Box::from_raw(ptr as *mut RiValidationResult);
286        }
287    }
288}
289
290// =============================================================================
291// RiSanitizationConfig JNI Bindings
292// =============================================================================
293
294#[no_mangle]
295pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_new0(
296    _env: JNIEnv,
297    _class: JClass,
298) -> jlong {
299    let config = Box::new(RiSanitizationConfig::default());
300    let ptr = Box::into_raw(config) as jlong;
301    register_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize);
302    ptr
303}
304
305#[no_mangle]
306pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_isTrimWhitespace0(
307    mut env: JNIEnv,
308    _class: JClass,
309    ptr: jlong,
310) -> jboolean {
311    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
312        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
313            .unwrap_or(());
314        return 0;
315    }
316
317    let config = unsafe { &*(ptr as *const RiSanitizationConfig) };
318    config.trim_whitespace as jboolean
319}
320
321#[no_mangle]
322pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_setTrimWhitespace0(
323    mut env: JNIEnv,
324    _class: JClass,
325    ptr: jlong,
326    enabled: jboolean,
327) {
328    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
329        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
330            .unwrap_or(());
331        return;
332    }
333
334    let config = unsafe { &mut *(ptr as *mut RiSanitizationConfig) };
335    config.trim_whitespace = enabled != 0;
336}
337
338#[no_mangle]
339pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_isLowercase0(
340    mut env: JNIEnv,
341    _class: JClass,
342    ptr: jlong,
343) -> jboolean {
344    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
345        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
346            .unwrap_or(());
347        return 0;
348    }
349
350    let config = unsafe { &*(ptr as *const RiSanitizationConfig) };
351    config.lowercase as jboolean
352}
353
354#[no_mangle]
355pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_setLowercase0(
356    mut env: JNIEnv,
357    _class: JClass,
358    ptr: jlong,
359    enabled: jboolean,
360) {
361    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
362        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
363            .unwrap_or(());
364        return;
365    }
366
367    let config = unsafe { &mut *(ptr as *mut RiSanitizationConfig) };
368    config.lowercase = enabled != 0;
369}
370
371#[no_mangle]
372pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_isUppercase0(
373    mut env: JNIEnv,
374    _class: JClass,
375    ptr: jlong,
376) -> jboolean {
377    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
378        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
379            .unwrap_or(());
380        return 0;
381    }
382
383    let config = unsafe { &*(ptr as *const RiSanitizationConfig) };
384    config.uppercase as jboolean
385}
386
387#[no_mangle]
388pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_setUppercase0(
389    mut env: JNIEnv,
390    _class: JClass,
391    ptr: jlong,
392    enabled: jboolean,
393) {
394    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
395        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
396            .unwrap_or(());
397        return;
398    }
399
400    let config = unsafe { &mut *(ptr as *mut RiSanitizationConfig) };
401    config.uppercase = enabled != 0;
402}
403
404#[no_mangle]
405pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_isRemoveHtmlTags0(
406    mut env: JNIEnv,
407    _class: JClass,
408    ptr: jlong,
409) -> jboolean {
410    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
411        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
412            .unwrap_or(());
413        return 0;
414    }
415
416    let config = unsafe { &*(ptr as *const RiSanitizationConfig) };
417    config.remove_html_tags as jboolean
418}
419
420#[no_mangle]
421pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_setRemoveHtmlTags0(
422    mut env: JNIEnv,
423    _class: JClass,
424    ptr: jlong,
425    enabled: jboolean,
426) {
427    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
428        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
429            .unwrap_or(());
430        return;
431    }
432
433    let config = unsafe { &mut *(ptr as *mut RiSanitizationConfig) };
434    config.remove_html_tags = enabled != 0;
435}
436
437#[no_mangle]
438pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_isEscapeSpecialChars0(
439    mut env: JNIEnv,
440    _class: JClass,
441    ptr: jlong,
442) -> jboolean {
443    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
444        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
445            .unwrap_or(());
446        return 0;
447    }
448
449    let config = unsafe { &*(ptr as *const RiSanitizationConfig) };
450    config.escape_special_chars as jboolean
451}
452
453#[no_mangle]
454pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_setEscapeSpecialChars0(
455    mut env: JNIEnv,
456    _class: JClass,
457    ptr: jlong,
458    enabled: jboolean,
459) {
460    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
461        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
462            .unwrap_or(());
463        return;
464    }
465
466    let config = unsafe { &mut *(ptr as *mut RiSanitizationConfig) };
467    config.escape_special_chars = enabled != 0;
468}
469
470#[no_mangle]
471pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizationConfig_free0(
472    _env: JNIEnv,
473    _class: JClass,
474    ptr: jlong,
475) {
476    if unregister_ptr(&SANITIZATION_CONFIG_REGISTRY, ptr as usize) {
477        unsafe {
478            let _ = Box::from_raw(ptr as *mut RiSanitizationConfig);
479        }
480    }
481}
482
483// =============================================================================
484// RiSanitizer JNI Bindings
485// =============================================================================
486
487#[no_mangle]
488pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizer_new0(
489    _env: JNIEnv,
490    _class: JClass,
491) -> jlong {
492    let sanitizer = Box::new(RiSanitizer::new());
493    let ptr = Box::into_raw(sanitizer) as jlong;
494    register_ptr(&SANITIZER_REGISTRY, ptr as usize);
495    ptr
496}
497
498#[no_mangle]
499pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizer_newWithConfig0(
500    mut env: JNIEnv,
501    _class: JClass,
502    config_ptr: jlong,
503) -> jlong {
504    if !is_valid_ptr(&SANITIZATION_CONFIG_REGISTRY, config_ptr as usize) {
505        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizationConfig pointer")
506            .unwrap_or(());
507        return 0;
508    }
509
510    let config = unsafe { &*(config_ptr as *const RiSanitizationConfig) };
511    let sanitizer = Box::new(RiSanitizer::with_config(config.clone()));
512    let ptr = Box::into_raw(sanitizer) as jlong;
513    register_ptr(&SANITIZER_REGISTRY, ptr as usize);
514    ptr
515}
516
517#[no_mangle]
518pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizer_sanitize0<'local>(
519    mut env: JNIEnv<'local>,
520    _class: JClass<'local>,
521    ptr: jlong,
522    input: JString,
523) -> jstring {
524    if !is_valid_ptr(&SANITIZER_REGISTRY, ptr as usize) {
525        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizer pointer")
526            .unwrap_or(());
527        return std::ptr::null_mut();
528    }
529
530    let input_str: String = match env.get_string(&input) {
531        Ok(s) => s.into(),
532        Err(_) => {
533            env.throw_new("java/lang/IllegalArgumentException", "Failed to get input string")
534                .unwrap_or(());
535            return std::ptr::null_mut();
536        }
537    };
538
539    let sanitizer = unsafe { &*(ptr as *const RiSanitizer) };
540    let result = sanitizer.sanitize(&input_str);
541    match env.new_string(&result) {
542        Ok(s) => s.into_raw(),
543        Err(_) => std::ptr::null_mut(),
544    }
545}
546
547#[no_mangle]
548pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizer_sanitizeEmail0<'local>(
549    mut env: JNIEnv<'local>,
550    _class: JClass<'local>,
551    ptr: jlong,
552    input: JString,
553) -> jstring {
554    if !is_valid_ptr(&SANITIZER_REGISTRY, ptr as usize) {
555        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizer pointer")
556            .unwrap_or(());
557        return std::ptr::null_mut();
558    }
559
560    let input_str: String = match env.get_string(&input) {
561        Ok(s) => s.into(),
562        Err(_) => {
563            env.throw_new("java/lang/IllegalArgumentException", "Failed to get input string")
564                .unwrap_or(());
565            return std::ptr::null_mut();
566        }
567    };
568
569    let sanitizer = unsafe { &*(ptr as *const RiSanitizer) };
570    let result = sanitizer.sanitize_email(&input_str);
571    match env.new_string(&result) {
572        Ok(s) => s.into_raw(),
573        Err(_) => std::ptr::null_mut(),
574    }
575}
576
577#[no_mangle]
578pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizer_sanitizeFilename0<'local>(
579    mut env: JNIEnv<'local>,
580    _class: JClass<'local>,
581    ptr: jlong,
582    input: JString,
583) -> jstring {
584    if !is_valid_ptr(&SANITIZER_REGISTRY, ptr as usize) {
585        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSanitizer pointer")
586            .unwrap_or(());
587        return std::ptr::null_mut();
588    }
589
590    let input_str: String = match env.get_string(&input) {
591        Ok(s) => s.into(),
592        Err(_) => {
593            env.throw_new("java/lang/IllegalArgumentException", "Failed to get input string")
594                .unwrap_or(());
595            return std::ptr::null_mut();
596        }
597    };
598
599    let sanitizer = unsafe { &*(ptr as *const RiSanitizer) };
600    let result = sanitizer.sanitize_filename(&input_str);
601    match env.new_string(&result) {
602        Ok(s) => s.into_raw(),
603        Err(_) => std::ptr::null_mut(),
604    }
605}
606
607#[no_mangle]
608pub extern "system" fn Java_com_dunimd_ri_validation_RiSanitizer_free0(
609    _env: JNIEnv,
610    _class: JClass,
611    ptr: jlong,
612) {
613    if unregister_ptr(&SANITIZER_REGISTRY, ptr as usize) {
614        unsafe {
615            let _ = Box::from_raw(ptr as *mut RiSanitizer);
616        }
617    }
618}
619
620// =============================================================================
621// RiSchemaValidator JNI Bindings
622// =============================================================================
623
624#[no_mangle]
625pub extern "system" fn Java_com_dunimd_ri_validation_RiSchemaValidator_new0(
626    mut env: JNIEnv,
627    _class: JClass,
628    schema_json: JString,
629) -> jlong {
630    let schema_str: String = match env.get_string(&schema_json) {
631        Ok(s) => s.into(),
632        Err(_) => {
633            env.throw_new("java/lang/IllegalArgumentException", "Failed to get schema string")
634                .unwrap_or(());
635            return 0;
636        }
637    };
638
639    let schema_value: serde_json::Value = match serde_json::from_str(&schema_str) {
640        Ok(v) => v,
641        Err(e) => {
642            let _ = env.throw_new(
643                "java/lang/IllegalArgumentException",
644                &format!("Failed to parse schema JSON: {}", e),
645            );
646            return 0;
647        }
648    };
649
650    let validator = RiSchemaValidator::new(schema_value);
651    let ptr = Box::into_raw(Box::new(validator)) as jlong;
652    register_ptr(&SCHEMA_VALIDATOR_REGISTRY, ptr as usize);
653    ptr
654}
655
656#[no_mangle]
657pub extern "system" fn Java_com_dunimd_ri_validation_RiSchemaValidator_validate0(
658    mut env: JNIEnv,
659    _class: JClass,
660    ptr: jlong,
661    data_json: JString,
662) -> jlong {
663    if !is_valid_ptr(&SCHEMA_VALIDATOR_REGISTRY, ptr as usize) {
664        env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiSchemaValidator pointer")
665            .unwrap_or(());
666        return 0;
667    }
668
669    let data_str: String = match env.get_string(&data_json) {
670        Ok(s) => s.into(),
671        Err(_) => {
672            env.throw_new("java/lang/IllegalArgumentException", "Failed to get data string")
673                .unwrap_or(());
674            return 0;
675        }
676    };
677
678    let validator = unsafe { &*(ptr as *const RiSchemaValidator) };
679    let data_value: serde_json::Value = match serde_json::from_str(&data_str) {
680        Ok(v) => v,
681        Err(_) => serde_json::Value::Null,
682    };
683    let result = Box::new(validator.validate(&data_value));
684    let result_ptr = Box::into_raw(result) as jlong;
685    register_ptr(&VALIDATION_RESULT_REGISTRY, result_ptr as usize);
686    result_ptr
687}
688
689#[no_mangle]
690pub extern "system" fn Java_com_dunimd_ri_validation_RiSchemaValidator_free0(
691    _env: JNIEnv,
692    _class: JClass,
693    ptr: jlong,
694) {
695    if unregister_ptr(&SCHEMA_VALIDATOR_REGISTRY, ptr as usize) {
696        unsafe {
697            let _ = Box::from_raw(ptr as *mut RiSchemaValidator);
698        }
699    }
700}
701
702// =============================================================================
703// RiValidationRunner JNI Bindings
704// =============================================================================
705
706#[no_mangle]
707pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationRunner_validate0(
708    mut env: JNIEnv,
709    _class: JClass,
710    ptr: jlong,
711    value: JString,
712) -> jlong {
713    if !is_valid_ptr(&VALIDATION_RUNNER_REGISTRY, ptr as usize) {
714        let _ = env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidationRunner pointer");
715        return 0;
716    }
717
718    let value_str = match get_string_arg(&mut env, &value, "value") {
719        Some(v) => v,
720        None => return 0,
721    };
722
723    let runner = unsafe { &*(ptr as *const RiValidationRunner) };
724    box_result(runner.validate_value(Some(&value_str)))
725}
726
727#[no_mangle]
728pub extern "system" fn Java_com_dunimd_ri_validation_RiValidationRunner_free0(
729    _env: JNIEnv,
730    _class: JClass,
731    ptr: jlong,
732) {
733    if unregister_ptr(&VALIDATION_RUNNER_REGISTRY, ptr as usize) {
734        unsafe {
735            let _ = Box::from_raw(ptr as *mut RiValidationRunner);
736        }
737    }
738}
739
740// =============================================================================
741// RiValidatorBuilder JNI Bindings
742// =============================================================================
743
744/// Stable slot holding an owned builder. The Rust builder API is consuming
745/// (methods take `self`), so each chained JNI call swaps the builder inside
746/// this fixed-address slot, keeping the Java-side pointer valid.
747pub struct BuilderSlot {
748    inner: std::sync::Mutex<Option<RiValidatorBuilder>>,
749}
750
751fn builder_slot_valid(env: &mut JNIEnv, ptr: jlong) -> bool {
752    if !is_valid_ptr(&VALIDATOR_BUILDER_REGISTRY, ptr as usize) {
753        let _ = env.throw_new("java/lang/IllegalStateException", "Invalid or freed RiValidatorBuilder pointer");
754        return false;
755    }
756    true
757}
758
759fn modify_builder(
760    env: &mut JNIEnv,
761    ptr: jlong,
762    f: impl FnOnce(RiValidatorBuilder) -> RiValidatorBuilder,
763) {
764    if !builder_slot_valid(env, ptr) {
765        return;
766    }
767    let slot = unsafe { &*(ptr as *const BuilderSlot) };
768    if let Ok(mut guard) = slot.inner.lock() {
769        if let Some(builder) = guard.take() {
770            *guard = Some(f(builder));
771        }
772    }
773}
774
775#[no_mangle]
776pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_new0(
777    mut env: JNIEnv,
778    _class: JClass,
779    field_name: JString,
780) -> jlong {
781    let Some(field_name) = get_string_arg(&mut env, &field_name, "fieldName") else { return 0 };
782    let slot = Box::new(BuilderSlot {
783        inner: std::sync::Mutex::new(Some(RiValidatorBuilder::new(&field_name))),
784    });
785    let ptr = Box::into_raw(slot) as jlong;
786    register_ptr(&VALIDATOR_BUILDER_REGISTRY, ptr as usize);
787    ptr
788}
789
790#[no_mangle]
791pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_notEmpty0(
792    mut env: JNIEnv,
793    _class: JClass,
794    ptr: jlong,
795) {
796    modify_builder(&mut env, ptr, |b| b.not_empty());
797}
798
799#[no_mangle]
800pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_minLength0(
801    mut env: JNIEnv,
802    _class: JClass,
803    ptr: jlong,
804    min: jint,
805) {
806    modify_builder(&mut env, ptr, |b| b.min_length(min.max(0) as usize));
807}
808
809#[no_mangle]
810pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_maxLength0(
811    mut env: JNIEnv,
812    _class: JClass,
813    ptr: jlong,
814    max: jint,
815) {
816    modify_builder(&mut env, ptr, |b| b.max_length(max.max(0) as usize));
817}
818
819#[no_mangle]
820pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_isEmail0(
821    mut env: JNIEnv,
822    _class: JClass,
823    ptr: jlong,
824) {
825    modify_builder(&mut env, ptr, |b| b.is_email());
826}
827
828#[no_mangle]
829pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_build0(
830    mut env: JNIEnv,
831    _class: JClass,
832    ptr: jlong,
833) -> jlong {
834    if !builder_slot_valid(&mut env, ptr) {
835        return 0;
836    }
837    let slot = unsafe { &*(ptr as *const BuilderSlot) };
838    let builder = match slot.inner.lock() {
839        Ok(mut guard) => guard.take(),
840        Err(_) => None,
841    };
842    match builder {
843        Some(builder) => {
844            let runner = builder.build();
845            let runner_ptr = Box::into_raw(Box::new(runner)) as jlong;
846            register_ptr(&VALIDATION_RUNNER_REGISTRY, runner_ptr as usize);
847            runner_ptr
848        }
849        None => {
850            let _ = env.throw_new("java/lang/IllegalStateException", "RiValidatorBuilder already consumed");
851            0
852        }
853    }
854}
855
856#[no_mangle]
857pub extern "system" fn Java_com_dunimd_ri_validation_RiValidatorBuilder_free0(
858    _env: JNIEnv,
859    _class: JClass,
860    ptr: jlong,
861) {
862    if unregister_ptr(&VALIDATOR_BUILDER_REGISTRY, ptr as usize) {
863        unsafe {
864            let _ = Box::from_raw(ptr as *mut BuilderSlot);
865        }
866    }
867}