questdb_confstr_ffi/
lib.rs

1/*******************************************************************************
2 *     ___                  _   ____  ____
3 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
4 *   | | | | | | |/ _ \/ __| __| | | |  _ \
5 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
6 *    \__\_\\__,_|\___||___/\__|____/|____/
7 *
8 *  Copyright (c) 2014-2019 Appsicle
9 *  Copyright (c)  2019-2025 QuestDB
10 *
11 *  Licensed under the Apache License, Version 2.0 (the "License");
12 *  you may not use this file except in compliance with the License.
13 *  You may obtain a copy of the License at
14 *
15 *  http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 *
23 ******************************************************************************/
24
25#![doc = include_str!("../README.md")]
26#![allow(clippy::missing_safety_doc)]
27
28use questdb_confstr::{parse_conf_str, ConfStr};
29use std::collections::hash_map;
30use std::os::raw::c_char;
31use std::ptr;
32use std::slice;
33
34#[repr(C)]
35pub struct questdb_conf_str {
36    inner: ConfStr,
37}
38
39#[repr(C)]
40pub struct questdb_conf_str_parse_err {
41    msg: *const c_char,
42    msg_len: usize,
43    pos: usize,
44}
45
46fn new_err(msg: String, pos: usize) -> *mut questdb_conf_str_parse_err {
47    let msg_len = msg.len();
48    let msg = Box::into_raw(msg.into_boxed_str()) as *const c_char;
49    Box::into_raw(Box::new(questdb_conf_str_parse_err { msg, msg_len, pos }))
50}
51
52#[no_mangle]
53pub unsafe extern "C" fn questdb_conf_str_parse_err_free(err: *mut questdb_conf_str_parse_err) {
54    if !err.is_null() {
55        let err = Box::from_raw(err);
56        drop(Box::from_raw(err.msg as *mut c_char));
57        drop(err);
58    }
59}
60
61#[no_mangle]
62pub unsafe extern "C" fn questdb_conf_str_parse(
63    str: *const c_char,
64    len: usize,
65    err_out: *mut *mut questdb_conf_str_parse_err,
66) -> *mut questdb_conf_str {
67    let input = slice::from_raw_parts(str as *const u8, len);
68    let input_str = match std::str::from_utf8(input) {
69        Ok(s) => s,
70        Err(utf8err) => {
71            let first_bad_byte = utf8err.valid_up_to();
72            *err_out = new_err(
73                format!("invalid UTF-8 sequence at position {}", first_bad_byte),
74                first_bad_byte,
75            );
76            return ptr::null_mut();
77        }
78    };
79
80    match parse_conf_str(input_str) {
81        Ok(conf_str) => Box::into_raw(Box::new(questdb_conf_str { inner: conf_str })),
82        Err(err) => {
83            *err_out = new_err(err.to_string(), err.position());
84            ptr::null_mut()
85        }
86    }
87}
88
89#[no_mangle]
90pub unsafe extern "C" fn questdb_conf_str_service(
91    conf_str: *const questdb_conf_str,
92    len_out: *mut usize,
93) -> *const c_char {
94    if conf_str.is_null() {
95        return ptr::null();
96    }
97
98    let conf_str = &(*conf_str).inner;
99    let service = conf_str.service();
100    *len_out = service.len();
101    service.as_ptr() as *const c_char
102}
103
104#[no_mangle]
105pub unsafe extern "C" fn questdb_conf_str_get(
106    conf_str: *const questdb_conf_str,
107    key: *const c_char,
108    key_len: usize,
109    val_len_out: *mut usize,
110) -> *const c_char {
111    if conf_str.is_null() || key.is_null() {
112        return ptr::null();
113    }
114
115    let conf_str = &(*conf_str).inner;
116    let key = slice::from_raw_parts(key as *const u8, key_len);
117    let key_str = match std::str::from_utf8(key) {
118        Ok(s) => s,
119        Err(_) => return ptr::null(),
120    };
121
122    match conf_str.get(key_str) {
123        Some(val) => {
124            let val_str = val.as_ptr() as *const c_char;
125            *val_len_out = val.len();
126            val_str
127        }
128        None => ptr::null(),
129    }
130}
131
132#[repr(C)]
133pub struct questdb_conf_str_iter {
134    inner: hash_map::Iter<'static, String, String>,
135}
136
137#[no_mangle]
138pub unsafe extern "C" fn questdb_conf_str_iter_pairs(
139    conf_str: *const questdb_conf_str,
140) -> *mut questdb_conf_str_iter {
141    if conf_str.is_null() {
142        return ptr::null_mut();
143    }
144    let conf_str = &(*conf_str).inner;
145    let iter = questdb_conf_str_iter {
146        inner: conf_str.params().iter(),
147    };
148    Box::into_raw(Box::new(iter))
149}
150
151#[no_mangle]
152pub unsafe extern "C" fn questdb_conf_str_iter_next(
153    iter: *mut questdb_conf_str_iter,
154    key_out: *mut *const c_char,
155    key_len_out: *mut usize,
156    val_out: *mut *const c_char,
157    val_len_out: *mut usize,
158) -> bool {
159    let iter = &mut *iter;
160    match iter.inner.next() {
161        Some((key, val)) => {
162            let key_str = key.as_ptr() as *const c_char;
163            let val_str = val.as_ptr() as *const c_char;
164            unsafe {
165                *key_out = key_str;
166                *key_len_out = key.len();
167                *val_out = val_str;
168                *val_len_out = val.len();
169            }
170            true
171        }
172        None => false,
173    }
174}
175
176#[no_mangle]
177pub unsafe extern "C" fn questdb_conf_str_iter_free(iter: *mut questdb_conf_str_iter) {
178    if !iter.is_null() {
179        drop(Box::from_raw(iter));
180    }
181}
182
183#[no_mangle]
184pub unsafe extern "C" fn questdb_conf_str_free(conf_str: *mut questdb_conf_str) {
185    if !conf_str.is_null() {
186        drop(Box::from_raw(conf_str));
187    }
188}