diff --git a/Cargo.toml b/Cargo.toml index c3338ba..258007b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ categories = ["encoding", "parser-implementations"] description = "Sonic-rs is a fast Rust JSON library based on SIMD" documentation = "https://docs.rs/sonic-rs" edition = "2021" -exclude = ["benchmarks", "assets"] +exclude = ["benchmarks", "assets", "bindings"] keywords = ["json", "simd", "serde", "serialization"] license = "Apache-2.0" name = "sonic-rs" diff --git a/bindings/README.md b/bindings/README.md new file mode 100644 index 0000000..8da9cf2 --- /dev/null +++ b/bindings/README.md @@ -0,0 +1,5 @@ +# A collections of bindings for sonic-rs + +1. ffi: the low-level APIs of sonic-rs, should not used directly +2. cpp: the C++ bindings of sonic-rs +... \ No newline at end of file diff --git a/bindings/ffi/Cargo.toml b/bindings/ffi/Cargo.toml new file mode 100644 index 0000000..f14230a --- /dev/null +++ b/bindings/ffi/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "sonic_rs_ffi" +version = "0.1.0" + +[dependencies] +sonic-rs = { path = "../../" } + +[build] +lib = ["staticlib"] + +[build-dependencies] +cbindgen = "0.27" diff --git a/bindings/ffi/build.rs b/bindings/ffi/build.rs new file mode 100644 index 0000000..29bdb23 --- /dev/null +++ b/bindings/ffi/build.rs @@ -0,0 +1,17 @@ +use std::env; + +use cbindgen::Language::C; + +fn main() { + setup_cbindgen(); +} + +fn setup_cbindgen() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(C) + .generate() + .expect("Unable to generate bindings") + .write_to_file("include/sonic_ffi.h"); +} diff --git a/bindings/ffi/cbindgen.toml b/bindings/ffi/cbindgen.toml new file mode 100644 index 0000000..2601989 --- /dev/null +++ b/bindings/ffi/cbindgen.toml @@ -0,0 +1,136 @@ +# This is a template cbindgen.toml file with all of the default values. +# Some values are commented out because their absence is the real default. +# +# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml +# for detailed documentation of every option here. + + +language = "C" + + +############## Options for Wrapping the Contents of the Header ################# + +# header = "/* Text to put at the beginning of the generated file. Probably a license. */" +# trailer = "/* Text to put at the end of the generated file */" +# include_guard = "my_bindings_h" +# pragma_once = true +# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = false +# namespace = "my_namespace" +includes = [] +namespaces = ["sonic_rs_ffi"] +no_includes = false +sys_includes = [] +using_namespaces = [] +# cpp_compat = true +after_includes = "" + + +############################ Code Style Options ################################ + +braces = "SameLine" +documentation = true +documentation_length = "full" +documentation_style = "auto" +line_endings = "LF" # also "CR", "CRLF", "Native" +line_length = 100 +tab_width = 2 + + +############################# Codegen Options ################################## + +sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` +style = "both" +usize_is_size_t = true + + +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + + +[export] +exclude = [] +include = [] +# prefix = "CAPI_" +item_types = [] +renaming_overrides_prefixing = false + + +[export.rename] + + +[export.body] + + +[export.mangle] + + +[fn] +rename_args = "None" +# must_use = "MUST_USE_FUNC" +# deprecated = "DEPRECATED_FUNC" +# deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE" +# no_return = "NO_RETURN" +# prefix = "START_FUNC" +# postfix = "END_FUNC" +args = "auto" +sort_by = "Name" + + +[struct] +rename_fields = "None" +# must_use = "MUST_USE_STRUCT" +# deprecated = "DEPRECATED_STRUCT" +# deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE" +derive_constructor = false +derive_eq = false +derive_gt = false +derive_gte = false +derive_lt = false +derive_lte = false +derive_neq = false + + +[enum] +rename_variants = "None" +# must_use = "MUST_USE_ENUM" +# deprecated = "DEPRECATED_ENUM" +# deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE" +add_sentinel = false +derive_const_casts = false +derive_helper_methods = false +derive_mut_casts = false +prefix_with_name = false +# cast_assert_name = "ASSERT" +derive_tagged_enum_copy_constructor = false +derive_tagged_enum_destructor = false +enum_class = true +private_default_tagged_enum_constructor = false + + +[const] +allow_constexpr = false +allow_static_const = true +sort_by = "Name" + + +[macro_expansion] +bitflags = false + + +############## Options for How Your Rust library Should Be Parsed ############## + +[parse] +parse_deps = false +# include = [] +clean = false +exclude = [] +extra_bindings = [] + + +[parse.expand] +all_features = false +crates = [] +default_features = true +features = [] diff --git a/bindings/ffi/include/sonic_ffi.h b/bindings/ffi/include/sonic_ffi.h new file mode 100644 index 0000000..e313e1c --- /dev/null +++ b/bindings/ffi/include/sonic_ffi.h @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#define SONIC_RS_DESERIALIZE_USE_RAW 1 + +#define SONIC_RS_DESERIALIZE_USE_RAWNUMBER 2 + +#define SONIC_RS_DESERIALIZE_UTF8_LOSSY 4 + +#define SONIC_RS_SERIALIZE_PRETTY 1 + +typedef struct SonicCString { + const void *buf; + uintptr_t len; +} SonicCString; + +typedef struct SonicDeserializeRet { + const void *value; + struct SonicCString err; +} SonicDeserializeRet; + +typedef struct SonicSerializeRet { + struct SonicCString json; + struct SonicCString err; +} SonicSerializeRet; + +/** + * # Safety + * + * The caller should drop the returned `value` or `err`. + */ +struct SonicDeserializeRet sonic_rs_deserialize_value(const char *json, + uintptr_t len, + uint64_t cfg); + +/** + * # Safety + * + * The caller should drop the returned `json` or `err`. + */ +struct SonicSerializeRet sonic_rs_serialize_value(const void *value, uint64_t cfg); + +/** + * # Safety + */ +void sonic_rs_drop_value(void *value); + +/** + * # Safety + */ +void sonic_rs_drop_string(void *buf, uint64_t len); diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs new file mode 100644 index 0000000..fa3d501 --- /dev/null +++ b/bindings/ffi/src/lib.rs @@ -0,0 +1,139 @@ +use std::{ffi::c_char, mem::ManuallyDrop, os::raw::c_void}; + +use sonic_rs::Value; + +/// A string allocated in Rust, ending with `\0`. Used for serialize output and error message. +#[derive(Debug)] +#[repr(C)] +pub struct SonicCString { + buf: *const c_void, + len: usize, +} + +impl Default for SonicCString { + fn default() -> Self { + SonicCString { + buf: std::ptr::null(), + len: 0, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct SonicDeserializeRet { + value: *const c_void, + err: SonicCString, +} + +pub const SONIC_RS_DESERIALIZE_USE_RAW: u64 = 1; +pub const SONIC_RS_DESERIALIZE_USE_RAWNUMBER: u64 = 2; +pub const SONIC_RS_DESERIALIZE_UTF8_LOSSY: u64 = 4; + +/// # Safety +/// +/// The caller should drop the returned `value` or `err`. +#[no_mangle] +pub unsafe extern "C" fn sonic_rs_deserialize_value( + json: *const c_char, + len: usize, + cfg: u64, +) -> SonicDeserializeRet { + let json = std::slice::from_raw_parts(json as *const u8, len); + let mut de = sonic_rs::serde::Deserializer::from_slice(json); + + if cfg & SONIC_RS_DESERIALIZE_USE_RAWNUMBER != 0 { + de = de.use_rawnumber(); + } + + if cfg & SONIC_RS_DESERIALIZE_USE_RAW != 0 { + de = de.use_raw(); + } + + if cfg & SONIC_RS_DESERIALIZE_UTF8_LOSSY != 0 { + de = de.utf8_lossy(); + } + + match de.deserialize::() { + Ok(value) => SonicDeserializeRet { + value: Box::into_raw(Box::new(value)) as *const _, + err: SonicCString::default(), + }, + Err(e) => { + // messega always end with '\0' + let msg = ManuallyDrop::new(format!("{}\0", e)); + let err = SonicCString { + buf: msg.as_ptr() as *const c_void, + len: msg.len(), + }; + SonicDeserializeRet { + value: std::ptr::null_mut(), + err, + } + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct SonicSerializeRet { + json: SonicCString, + err: SonicCString, +} + +pub const SONIC_RS_SERIALIZE_PRETTY: u64 = 1; + +/// # Safety +/// +/// The caller should drop the returned `json` or `err`. +#[no_mangle] +pub unsafe extern "C" fn sonic_rs_serialize_value( + value: *const c_void, + cfg: u64, +) -> SonicSerializeRet { + let value = unsafe { &*(value as *const Value) }; + let ret = if cfg & SONIC_RS_SERIALIZE_PRETTY != 0 { + sonic_rs::to_string_pretty(value) + } else { + sonic_rs::to_string(value) + }; + + match ret { + Ok(json) => { + let json = ManuallyDrop::new(json); + let json = SonicCString { + buf: json.as_ptr() as *const c_void, + len: json.len(), + }; + SonicSerializeRet { + json, + err: SonicCString::default(), + } + } + Err(e) => { + // NOTE: should be dropped manually in the foreign caller + let msg = ManuallyDrop::new(format!("{}\0", e)); + let err = SonicCString { + buf: msg.as_ptr() as *const c_void, + len: msg.len(), + }; + SonicSerializeRet { + json: SonicCString::default(), + err, + } + } + } +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn sonic_rs_drop_value(value: *mut c_void) { + std::mem::drop(Box::from_raw(value as *mut Value)); +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn sonic_rs_drop_string(buf: *mut c_void, len: u64) { + let s = Vec::from_raw_parts(buf as *mut u8, len, len); + std::mem::drop(s); +}