mirror of https://github.com/dnomd343/ClearDNS
Dnomd343
2 years ago
42 changed files with 2042 additions and 350 deletions
@ -1,7 +1,9 @@ |
|||
/bin/ |
|||
/build/ |
|||
/.idea/ |
|||
/assets/*.txt |
|||
/src/target/ |
|||
/cmake-build/ |
|||
/cmake-build-debug/ |
|||
/cmake-build-release/ |
|||
/src/to-json/target/ |
|||
|
|||
/assets/*.txt |
|||
/include/constant.h |
|||
|
@ -1,6 +1,41 @@ |
|||
cmake_minimum_required(VERSION 2.8.12) |
|||
project(cleardns) |
|||
|
|||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) |
|||
project(cleardns LANGUAGES C) |
|||
|
|||
set(CMAKE_C_STANDARD 99) |
|||
|
|||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/) |
|||
|
|||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror") |
|||
|
|||
############################################################### |
|||
|
|||
if(NOT CMAKE_BUILD_TYPE) |
|||
set(CMAKE_BUILD_TYPE Release) |
|||
endif() |
|||
|
|||
############################################################### |
|||
|
|||
macro(git_tag _tag) |
|||
find_package(Git QUIET) |
|||
if (GIT_FOUND) |
|||
execute_process( |
|||
COMMAND ${GIT_EXECUTABLE} describe --tags |
|||
OUTPUT_VARIABLE ${_tag} |
|||
OUTPUT_STRIP_TRAILING_WHITESPACE |
|||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} |
|||
) |
|||
endif() |
|||
endmacro() |
|||
|
|||
set(VERSION "") |
|||
git_tag(VERSION) |
|||
if(VERSION STREQUAL "") # without git tag |
|||
message(FATAL_ERROR "Git command not found") |
|||
endif() |
|||
|
|||
############################################################### |
|||
|
|||
add_subdirectory(src) |
|||
|
|||
############################################################### |
|||
|
@ -1,12 +1,17 @@ |
|||
#pragma once |
|||
|
|||
/* Generated with cbindgen:0.23.0 */ |
|||
|
|||
#include <stdarg.h> |
|||
#include <stdbool.h> |
|||
#include <stdint.h> |
|||
#include <stdlib.h> |
|||
|
|||
/**
|
|||
* Free the exported c-style string. |
|||
*/ |
|||
void free_rust_string(const char *ptr); |
|||
|
|||
const char *to_json_ffi(const char *content); |
|||
/**
|
|||
* Format the input text into JSON format and return a c-style string, or return |
|||
* `NULL` if an error occurs. |
|||
*/ |
|||
const char *to_json(const char *content); |
|||
|
@ -1,15 +1,29 @@ |
|||
#ifndef ASSETS_H_ |
|||
#define ASSETS_H_ |
|||
|
|||
typedef struct { |
|||
char **update_file; |
|||
char **update_url; |
|||
} assets; |
|||
#include <stdint.h> |
|||
|
|||
extern char **custom_gfwlist; |
|||
extern char **custom_china_ip; |
|||
extern char **custom_chinalist; |
|||
|
|||
assets* assets_init(); |
|||
void assets_free(assets *info); |
|||
void assets_load(assets *info); |
|||
typedef struct { |
|||
char *file; // string
|
|||
char **sources; // string list
|
|||
} asset; |
|||
|
|||
void assets_extract(); |
|||
void assets_load(asset **info); |
|||
|
|||
asset** assets_init(); |
|||
asset* asset_init(const char *name); |
|||
void assets_dump(asset **asset_list); |
|||
void assets_free(asset **asset_list); |
|||
uint32_t assets_size(asset **asset_list); |
|||
void assets_append(asset ***asset_list, asset *res); |
|||
|
|||
/// Rust assets interface
|
|||
void assets_log_init(uint8_t verbose); |
|||
uint8_t asset_update(const char *file, char *const *sources, const char *assets_dir); |
|||
|
|||
#endif |
|||
|
File diff suppressed because it is too large
@ -0,0 +1,6 @@ |
|||
[workspace] |
|||
|
|||
members = [ |
|||
"assets", |
|||
"to-json" |
|||
] |
@ -0,0 +1,15 @@ |
|||
[package] |
|||
name = "assets" |
|||
version = "0.1.0" |
|||
edition = "2021" |
|||
|
|||
[lib] |
|||
crate-type = ["staticlib"] |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
|||
env_logger = "0.10.0" |
|||
log = "0.4.17" |
|||
reqwest = { version = "0.11.14", features = ["deflate", "gzip", "brotli"] } |
|||
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } |
@ -0,0 +1,3 @@ |
|||
language = "C" |
|||
pragma_once = true |
|||
include_version = false |
@ -0,0 +1,193 @@ |
|||
use std::fs::File; |
|||
use std::io::Read; |
|||
use reqwest::Client; |
|||
use std::time::Duration; |
|||
use log::{debug, info, warn}; |
|||
use std::collections::HashSet; |
|||
|
|||
/// Http download timeout limit
|
|||
const TIMEOUT: u64 = 120; |
|||
|
|||
/// Cut text line by line and remove invisible characters on both sides.
|
|||
fn asset_tidy(data: &str) -> Vec<String> { |
|||
data.lines() |
|||
.map(|x| String::from(x.trim())) |
|||
.filter(|x| !x.is_empty()) |
|||
.collect() |
|||
} |
|||
|
|||
/// Remove duplicate elements from an array.
|
|||
fn remove_dup(data: &Vec<String>) -> Vec<String> { |
|||
let mut result: Vec<String> = vec![]; |
|||
let mut tmp: HashSet<String> = HashSet::new(); |
|||
for val in data { |
|||
if tmp.insert(val.clone()) { // value already exist
|
|||
result.push(val.clone()); |
|||
} |
|||
} |
|||
result |
|||
} |
|||
|
|||
/// Download the specified text file and organize it into a String array.
|
|||
async fn http_fetch(url: &str, timeout: u64) -> Result<Vec<String>, String> { |
|||
let client = Client::builder() |
|||
.timeout(Duration::from_secs(timeout)) |
|||
.build().unwrap(); |
|||
debug!("Start downloading `{}`", url); |
|||
match client.get(url).send().await { |
|||
Ok(response) => { |
|||
match response.text().await { |
|||
Ok(text) => { |
|||
info!("Remote file `{}` download success", url); |
|||
Ok(asset_tidy(&text)) |
|||
}, |
|||
Err(err) => Err(format!("http content error: {}", err)) |
|||
} |
|||
}, |
|||
Err(err) => Err(format!("http request failed: {}", err)) |
|||
} |
|||
} |
|||
|
|||
/// Read the specified text file and organize it into a String array.
|
|||
async fn local_fetch(path: &str) -> Result<Vec<String>, String> { |
|||
match File::open(path) { |
|||
Ok(mut file) => { |
|||
let mut text = String::new(); |
|||
if let Err(err) = file.read_to_string(&mut text) { |
|||
return Err(format!("file `{}` read failed: {}", path, err)); |
|||
}; |
|||
info!("Local file `{}` read success", path); |
|||
Ok(asset_tidy(&text)) |
|||
}, |
|||
Err(err) => Err(format!("file `{}` open failed: {}", path, err)), |
|||
} |
|||
} |
|||
|
|||
/// Get multiple resource data and merge them.
|
|||
pub(crate) async fn asset_fetch(name: &str, sources: &Vec<String>) -> Option<Vec<String>> { |
|||
let is_remote = |src: &str| { |
|||
src.starts_with("http://") || src.starts_with("https://") |
|||
}; |
|||
let mut contents: Vec<Vec<String>> = vec![]; |
|||
for source in sources { |
|||
contents.push(match if is_remote(&source) { |
|||
http_fetch(source.trim(), TIMEOUT).await // from remote text file
|
|||
} else { |
|||
local_fetch(source.trim()).await // from local text file
|
|||
} { |
|||
Ok(data) => { |
|||
debug!("Asset source `{}` fetch success with {} items", source.trim(), data.len()); |
|||
data |
|||
}, |
|||
Err(err) => { |
|||
warn!("Asset source `{}` fetch failed: {}", source.trim(), err); |
|||
return None; // stop fetch process
|
|||
} |
|||
}); |
|||
} |
|||
let contents = remove_dup(&contents |
|||
.into_iter() |
|||
.flatten() |
|||
.collect::<Vec<String>>()); |
|||
info!("Asset `{}` fetch complete with {} items", name, contents.len()); |
|||
Some(contents) |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use std::fs; |
|||
use std::pin::Pin; |
|||
use std::future::Future; |
|||
use std::fs::OpenOptions; |
|||
use std::io::Write; |
|||
use super::{asset_tidy, remove_dup}; |
|||
use super::{http_fetch, local_fetch, asset_fetch}; |
|||
|
|||
const TEST_DATA: &str = "\tabc \n123 \n 456\r\nabc\n\n789 "; |
|||
|
|||
#[test] |
|||
fn basic() { |
|||
assert_eq!(asset_tidy(TEST_DATA), vec![ |
|||
String::from("abc"), |
|||
String::from("123"), |
|||
String::from("456"), |
|||
String::from("abc"), |
|||
String::from("789"), |
|||
]); |
|||
assert_eq!(remove_dup(&asset_tidy(TEST_DATA)), vec![ |
|||
String::from("abc"), |
|||
String::from("123"), |
|||
String::from("456"), |
|||
String::from("789"), |
|||
]); |
|||
} |
|||
|
|||
fn run_async<T>(func: Pin<Box<impl Future<Output=T>>>) { |
|||
tokio::runtime::Builder::new_multi_thread() |
|||
.enable_all() |
|||
.build() |
|||
.unwrap() |
|||
.block_on(func); |
|||
} |
|||
|
|||
fn gen_test_file() { |
|||
let mut fp = OpenOptions::new() |
|||
.write(true) |
|||
.create(true) |
|||
.truncate(true) |
|||
.open("/tmp/assets_test_file") |
|||
.unwrap(); |
|||
fp.write_all(TEST_DATA.as_ref()).expect("test file create error"); |
|||
} |
|||
|
|||
#[test] |
|||
fn asset() { |
|||
// test http_fetch function
|
|||
run_async(Box::pin(async { |
|||
assert!(http_fetch("invalid url", 10).await.is_err()); |
|||
assert_eq!( |
|||
http_fetch("https://gstatic.com/generate_204", 10).await, |
|||
Ok(vec![]) |
|||
); |
|||
})); |
|||
|
|||
// test local_fetch function
|
|||
gen_test_file(); |
|||
run_async(Box::pin(async { |
|||
assert!(local_fetch("/").await.is_err()); |
|||
assert_eq!( |
|||
local_fetch("/tmp/assets_test_file").await, |
|||
Ok(vec![ |
|||
String::from("abc"), |
|||
String::from("123"), |
|||
String::from("456"), |
|||
String::from("abc"), |
|||
String::from("789"), |
|||
]) |
|||
); |
|||
})); |
|||
|
|||
// test combine asset_fetch function
|
|||
run_async(Box::pin(async { |
|||
assert!( |
|||
asset_fetch("", &vec![]).await.unwrap().is_empty() |
|||
); |
|||
assert!( |
|||
asset_fetch("", &vec![String::from("...")]).await.is_none() |
|||
); |
|||
assert_eq!( |
|||
asset_fetch("", &vec![ |
|||
String::from("/tmp/assets_test_file"), |
|||
String::from("https://gstatic.com/generate_204") |
|||
]).await, |
|||
Some(vec![ |
|||
String::from("abc"), |
|||
String::from("123"), |
|||
String::from("456"), |
|||
String::from("789"), |
|||
]) |
|||
); |
|||
})); |
|||
fs::remove_file("/tmp/assets_test_file").expect("test file delete error"); |
|||
} |
|||
} |
@ -0,0 +1,97 @@ |
|||
use std::io::Write; |
|||
use std::env::set_var; |
|||
use std::path::PathBuf; |
|||
use std::fs::OpenOptions; |
|||
use std::os::raw::c_char; |
|||
use log::{debug, trace, warn}; |
|||
use std::ffi::{CStr, CString}; |
|||
use crate::fetch::asset_fetch; |
|||
|
|||
/// Compatible with C89 bool value.
|
|||
const TRUE: u8 = 1; |
|||
const FALSE: u8 = 0; |
|||
|
|||
/// Load c-style string from `char *` pointer.
|
|||
unsafe fn load_c_string(ptr: *const c_char) -> String { |
|||
CString::from(CStr::from_ptr(ptr)) |
|||
.into_string() |
|||
.unwrap() |
|||
} |
|||
|
|||
/// Load c-style string list from `char **` pointer.
|
|||
unsafe fn load_c_string_list(ptr: *const *const c_char) -> Vec<String> { |
|||
let mut index = 0; |
|||
let mut string_list: Vec<String> = vec![]; |
|||
while *ptr.offset(index) != std::ptr::null() { // traverse until `NULL`
|
|||
string_list.push(load_c_string(*ptr.offset(index))); |
|||
index += 1; |
|||
} |
|||
string_list |
|||
} |
|||
|
|||
/// Initialize the rust module log, enable trace level log when verbose is not `0`.
|
|||
#[no_mangle] |
|||
pub unsafe extern "C" fn assets_log_init(verbose: u8) { |
|||
if verbose == FALSE { // bool value `FALSE`
|
|||
set_var("RUST_LOG", "info"); |
|||
} else { |
|||
set_var("RUST_LOG", "trace"); |
|||
} |
|||
env_logger::init(); |
|||
} |
|||
|
|||
/// Update the specified resource file, return `0` on failure.
|
|||
#[no_mangle] |
|||
#[tokio::main] |
|||
pub async unsafe extern "C" fn asset_update( |
|||
file: *const c_char, sources: *const *const c_char, assets_dir: *const c_char) -> u8 { |
|||
|
|||
let name = load_c_string(file); // import c-style string
|
|||
let sources = load_c_string_list(sources); |
|||
let assets_dir = load_c_string(assets_dir); |
|||
trace!("Working folder is `{}`", assets_dir); |
|||
trace!("Updating `{}` from {:?}", name, sources); |
|||
|
|||
let assets_dir = PathBuf::from(&assets_dir); |
|||
let is_remote = |src: &str| { |
|||
src.starts_with("http://") || src.starts_with("https://") |
|||
}; |
|||
let sources = sources.iter() |
|||
.map(|src| { |
|||
if !is_remote(&src) && !src.starts_with("/") { // local relative path
|
|||
let file_path = assets_dir.join(src); |
|||
String::from(file_path.to_str().unwrap()) |
|||
} else { |
|||
src.clone() |
|||
} |
|||
}) |
|||
.collect::<Vec<String>>(); |
|||
let file = String::from(assets_dir.join(&name).to_str().unwrap()); |
|||
debug!("Asset sources -> {:?}", sources); |
|||
debug!("Asset target -> `{}`", file); |
|||
|
|||
match asset_fetch(&name, &sources).await { |
|||
Some(data) => { |
|||
let mut content = String::new(); |
|||
let _ = data.iter().map(|item| { |
|||
content.push_str(item); |
|||
content.push('\n'); |
|||
}).collect::<Vec<()>>(); |
|||
match OpenOptions::new() |
|||
.write(true) |
|||
.create(true) |
|||
.truncate(true) |
|||
.open(&file) { // open target file
|
|||
Ok(mut fp) => { |
|||
match fp.write_all(content.as_ref()) { |
|||
Err(err) => warn!("File `{}` save error: {}", file, err), |
|||
_ => debug!("File `{}` save success", file), |
|||
} |
|||
}, |
|||
Err(err) => warn!("File `{}` open failed: {}", file, err), |
|||
}; |
|||
TRUE // asset fetch success
|
|||
}, |
|||
None => FALSE, // asset fetch failed
|
|||
} |
|||
} |
@ -0,0 +1,2 @@ |
|||
mod ffi; |
|||
mod fetch; |
@ -1,91 +0,0 @@ |
|||
# This file is automatically @generated by Cargo. |
|||
# It is not intended for manual editing. |
|||
version = 3 |
|||
|
|||
[[package]] |
|||
name = "autocfg" |
|||
version = "1.1.0" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" |
|||
|
|||
[[package]] |
|||
name = "hashbrown" |
|||
version = "0.12.3" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" |
|||
|
|||
[[package]] |
|||
name = "indexmap" |
|||
version = "1.9.2" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" |
|||
dependencies = [ |
|||
"autocfg", |
|||
"hashbrown", |
|||
] |
|||
|
|||
[[package]] |
|||
name = "itoa" |
|||
version = "1.0.4" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" |
|||
|
|||
[[package]] |
|||
name = "ryu" |
|||
version = "1.0.11" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" |
|||
|
|||
[[package]] |
|||
name = "serde" |
|||
version = "1.0.149" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" |
|||
|
|||
[[package]] |
|||
name = "serde_json" |
|||
version = "1.0.89" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" |
|||
dependencies = [ |
|||
"itoa", |
|||
"ryu", |
|||
"serde", |
|||
] |
|||
|
|||
[[package]] |
|||
name = "serde_yaml" |
|||
version = "0.9.14" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" |
|||
dependencies = [ |
|||
"indexmap", |
|||
"itoa", |
|||
"ryu", |
|||
"serde", |
|||
"unsafe-libyaml", |
|||
] |
|||
|
|||
[[package]] |
|||
name = "to-json" |
|||
version = "0.1.0" |
|||
dependencies = [ |
|||
"serde_json", |
|||
"serde_yaml", |
|||
"toml", |
|||
] |
|||
|
|||
[[package]] |
|||
name = "toml" |
|||
version = "0.5.9" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" |
|||
dependencies = [ |
|||
"serde", |
|||
] |
|||
|
|||
[[package]] |
|||
name = "unsafe-libyaml" |
|||
version = "0.2.4" |
|||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|||
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" |
@ -1,3 +1,3 @@ |
|||
language = "C" |
|||
pragma_once = true |
|||
include_version = true |
|||
include_version = false |
|||
|
@ -1,27 +1,130 @@ |
|||
use crate::json::to_json; |
|||
use std::ffi::{c_char, CStr, CString}; |
|||
use std::os::raw::c_char; |
|||
use std::ffi::{CStr, CString}; |
|||
use crate::parser::{parser, Value}; |
|||
|
|||
fn to_c_string(string: String) -> *const c_char { // fetch c-style ptr of string
|
|||
CString::new(string).unwrap().into_raw() |
|||
/// Load c-style string from `const char *` pointer.
|
|||
#[inline] |
|||
unsafe fn load_c_string(ptr: *const c_char) -> String { |
|||
CString::from(CStr::from_ptr(ptr)) |
|||
.into_string() |
|||
.unwrap() |
|||
} |
|||
|
|||
unsafe fn load_c_string(ptr: *const c_char) -> String { // load string from c-style ptr
|
|||
String::from( |
|||
CStr::from_ptr(ptr).to_str().unwrap() |
|||
) |
|||
/// Export c-style string as `const char *` pointer.
|
|||
/// # NOTE
|
|||
/// The exported string cannot be freed by the c language `free(void *)` function,
|
|||
/// but should use the `free_rust_string` callback function, if this interface is
|
|||
/// not called, a memory leak will occur.
|
|||
#[inline] |
|||
fn export_c_string(string: String) -> *const c_char { |
|||
CString::new(string).unwrap().into_raw() |
|||
} |
|||
|
|||
/// Free the exported c-style string.
|
|||
#[no_mangle] |
|||
pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) { // free string memory
|
|||
let _ = CString::from_raw(ptr as *mut _); |
|||
pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) { |
|||
let _ = CString::from_raw(ptr as *mut _); // reclaim rust ownership
|
|||
} |
|||
|
|||
/// Deserialize text content and serialize to JSON format.
|
|||
fn json_format(content: &str) -> Option<String> { |
|||
let result = match parser(&content) { |
|||
Ok(value) => match value { |
|||
Value::JSON(json) => serde_json::to_string(&json), |
|||
Value::YAML(yaml) => serde_json::to_string(&yaml), |
|||
Value::TOML(toml) => serde_json::to_string(&toml), |
|||
}, |
|||
_ => return None, |
|||
}; |
|||
match result { |
|||
Ok(data) => Some(data), |
|||
Err(_) => None, |
|||
} |
|||
} |
|||
|
|||
/// Format the input text into JSON format and return a c-style string, or return
|
|||
/// `NULL` if an error occurs.
|
|||
#[no_mangle] |
|||
pub unsafe extern "C" fn to_json_ffi(content: *const c_char) -> *const c_char { |
|||
pub unsafe extern "C" fn to_json(content: *const c_char) -> *const c_char { |
|||
let content = load_c_string(content); |
|||
let result = match to_json(&content) { // convert to JSON format
|
|||
match json_format(&content) { |
|||
Some(data) => export_c_string(data), |
|||
None => std::ptr::null(), |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::json_format; |
|||
|
|||
const JSON_TEST_STR: &str = r#" |
|||
{ |
|||
"int": 123, |
|||
"bool": true, |
|||
"float": 3.141592, |
|||
"string": "json test", |
|||
"array": [1, 2, 3, 4, 5], |
|||
"object": { |
|||
"sub": "test" |
|||
} |
|||
} |
|||
"#; |
|||
|
|||
const YAML_TEST_STR: &str = r#" |
|||
int: 123 |
|||
bool: true |
|||
float: 3.141592 |
|||
string: "json test" |
|||
array: |
|||
- 1 |
|||
- 2 |
|||
- 3 |
|||
- 4 |
|||
- 5 |
|||
object: |
|||
sub: test |
|||
"#; |
|||
|
|||
const TOML_TEST_STR: &str = r#" |
|||
int = 123 |
|||
bool = true |
|||
float = 3.141592 |
|||
string = "json test" |
|||
array = [ 1, 2, 3, 4, 5 ] |
|||
|
|||
[object] |
|||
sub = "test" |
|||
"#; |
|||
|
|||
#[inline] |
|||
fn format(raw: &str) -> String { |
|||
match json_format(raw) { |
|||
Some(data) => data, |
|||
None => String::new(), // convert failed -> empty string
|
|||
}; |
|||
to_c_string(result) // return c-style ptr
|
|||
None => panic!("format error"), |
|||
} |
|||
} |
|||
|
|||
#[test] |
|||
fn json_input() { |
|||
assert_eq!( |
|||
format(JSON_TEST_STR), |
|||
format(&json_format(JSON_TEST_STR).unwrap()), |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn yaml_input() { |
|||
assert_eq!( |
|||
format(JSON_TEST_STR), |
|||
format(&json_format(YAML_TEST_STR).unwrap()), |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn toml_input() { |
|||
assert_eq!( |
|||
format(JSON_TEST_STR), |
|||
format(&json_format(TOML_TEST_STR).unwrap()), |
|||
); |
|||
} |
|||
} |
|||
|
@ -1,21 +0,0 @@ |
|||
use serde_json as json; |
|||
use crate::parser::{parser, Value}; |
|||
|
|||
fn json_convert(content: &str) -> Result<String, String> { // convert to JSON format
|
|||
let data = match parser(content)? { |
|||
Value::JSON(_json) => json::to_string(&_json), |
|||
Value::YAML(_yaml) => json::to_string(&_yaml), |
|||
Value::TOML(_toml) => json::to_string(&_toml), |
|||
}; |
|||
match data { |
|||
Ok(data) => Ok(data), |
|||
Err(err) => Err(err.to_string()), |
|||
} |
|||
} |
|||
|
|||
pub fn to_json(content: &str) -> Option<String> { // to JSON string
|
|||
match json_convert(content) { |
|||
Ok(data) => Some(data), |
|||
Err(_) => None, // convert failed
|
|||
} |
|||
} |
@ -1,4 +1,2 @@ |
|||
mod ffi; |
|||
mod json; |
|||
mod tests; |
|||
mod parser; |
|||
|
@ -1,80 +0,0 @@ |
|||
use crate::json::to_json; |
|||
|
|||
#[allow(dead_code)] |
|||
const JSON_TEST_CONTENT: &str = r#" |
|||
{ |
|||
"int": 123, |
|||
"bool": true, |
|||
"float": 3.141592, |
|||
"string": "json test", |
|||
"array": [1, 2, 3, 4, 5], |
|||
"object": { |
|||
"sub": "test" |
|||
} |
|||
} |
|||
"#; |
|||
|
|||
#[allow(dead_code)] |
|||
const YAML_TEST_CONTENT: &str = r#" |
|||
int: 123 |
|||
bool: true |
|||
float: 3.141592 |
|||
string: "json test" |
|||
array: |
|||
- 1 |
|||
- 2 |
|||
- 3 |
|||
- 4 |
|||
- 5 |
|||
object: |
|||
sub: test |
|||
"#; |
|||
|
|||
#[allow(dead_code)] |
|||
const TOML_TEST_CONTENT: &str = r#" |
|||
int = 123 |
|||
bool = true |
|||
float = 3.141592 |
|||
string = "json test" |
|||
array = [ 1, 2, 3, 4, 5 ] |
|||
|
|||
[object] |
|||
sub = "test" |
|||
"#; |
|||
|
|||
|
|||
mod tests { |
|||
use super::*; |
|||
|
|||
#[allow(dead_code)] |
|||
fn format_json(raw: &str) -> String { |
|||
match to_json(raw) { |
|||
Some(data) => data, |
|||
None => panic!("JSON format error"), |
|||
} |
|||
} |
|||
|
|||
#[test] |
|||
fn json_to_json() { |
|||
assert_eq!( |
|||
format_json(JSON_TEST_CONTENT), |
|||
format_json(&to_json(JSON_TEST_CONTENT).unwrap()), |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn yaml_to_json() { |
|||
assert_eq!( |
|||
format_json(JSON_TEST_CONTENT), |
|||
format_json(&to_json(YAML_TEST_CONTENT).unwrap()), |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn toml_to_json() { |
|||
assert_eq!( |
|||
format_json(JSON_TEST_CONTENT), |
|||
format_json(&to_json(TOML_TEST_CONTENT).unwrap()), |
|||
); |
|||
} |
|||
} |
@ -1,3 +1,4 @@ |
|||
cmake_minimum_required(VERSION 2.8.12) |
|||
|
|||
add_library(utils cJSON.c assets.c logger.c process.c) |
|||
target_link_libraries(utils assets ssl crypto) |
|||
|
Loading…
Reference in new issue