use std::ffi::c_char;
use geopackage::ValueRef;
use geopackage::core::datetime::{Date, DateTime};
use crate::error::{Status, gpkg_error_t, set_error};
use crate::util::borrow_str;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct gpkg_value_t {
pub kind: ValueKind,
pub value: gpkg_value_payload,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueKind {
Null = 0,
Boolean = 1,
Integer = 2,
Float = 3,
Text = 4,
Blob = 5,
Date = 6,
DateTime = 7,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union gpkg_value_payload {
pub boolean: bool,
pub integer: i64,
pub real: f64,
pub text: *const c_char,
pub blob: gpkg_blob_t,
pub date: gpkg_date_t,
pub datetime: gpkg_datetime_t,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct gpkg_date_t {
pub year: u16,
pub month: u8,
pub day: u8,
}
impl gpkg_date_t {
fn to_date(self) -> Result<Date, String> {
Date::new(self.year, self.month, self.day).map_err(|source| {
format!(
"{:04}-{:02}-{:02} is not a date: {source}",
self.year, self.month, self.day
)
})
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct gpkg_datetime_t {
pub date: gpkg_date_t,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub nanosecond: u32,
pub has_offset: bool,
pub offset_minutes: i16,
}
impl gpkg_datetime_t {
fn to_datetime(self) -> Result<DateTime, String> {
let date = self.date.to_date()?;
if self.hour > 23 || self.minute > 59 || self.second > 59 {
return Err(format!(
"{:02}:{:02}:{:02} is not a time of day",
self.hour, self.minute, self.second
));
}
if self.nanosecond > 999_999_999 {
return Err(format!("{} is not a nanosecond count", self.nanosecond));
}
Ok(DateTime {
date,
hour: self.hour,
minute: self.minute,
second: self.second,
nanosecond: self.nanosecond,
offset_minutes: self.has_offset.then_some(self.offset_minutes),
})
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct gpkg_blob_t {
pub data: *const u8,
pub len: usize,
}
pub(crate) unsafe fn borrow_value<'a>(
value: *const gpkg_value_t,
index: usize,
error: *mut gpkg_error_t,
) -> Result<ValueRef<'a>, Status> {
if value.is_null() {
let message = format!("value {index} is NULL");
unsafe { set_error(error, Status::BadArgument, &message) };
return Err(Status::BadArgument);
}
let value = unsafe { &*value };
let rejected = |what: String, error: *mut gpkg_error_t| {
let message = format!("value {index}: {what}");
unsafe { set_error(error, Status::InvalidArgument, &message) };
Err(Status::InvalidArgument)
};
match value.kind {
ValueKind::Null => Ok(ValueRef::Null),
ValueKind::Boolean => Ok(ValueRef::Boolean(unsafe { value.value.boolean })),
ValueKind::Integer => Ok(ValueRef::Integer(unsafe { value.value.integer })),
ValueKind::Float => Ok(ValueRef::Float(unsafe { value.value.real })),
ValueKind::Text => {
let ptr = unsafe { value.value.text };
let text =
unsafe { borrow_str(ptr, error, "value text") }.ok_or(Status::BadArgument)?;
Ok(ValueRef::Text(text))
}
ValueKind::Blob => {
let blob = unsafe { value.value.blob };
if blob.len == 0 {
return Ok(ValueRef::Blob(&[]));
}
if blob.data.is_null() {
return rejected("blob is NULL with a non-zero length".to_owned(), error);
}
Ok(ValueRef::Blob(unsafe {
std::slice::from_raw_parts(blob.data, blob.len)
}))
}
ValueKind::Date => match unsafe { value.value.date }.to_date() {
Ok(date) => Ok(ValueRef::Date(date)),
Err(why) => rejected(why, error),
},
ValueKind::DateTime => match unsafe { value.value.datetime }.to_datetime() {
Ok(datetime) => Ok(ValueRef::DateTime(datetime)),
Err(why) => rejected(why, error),
},
}
}
pub(crate) unsafe fn borrow_values<'a>(
values: *const gpkg_value_t,
len: usize,
error: *mut gpkg_error_t,
) -> Result<Vec<ValueRef<'a>>, Status> {
if len == 0 {
return Ok(Vec::new());
}
if values.is_null() {
unsafe { set_error(error, Status::BadArgument, "values is NULL") };
return Err(Status::BadArgument);
}
let mut out = Vec::with_capacity(len);
for index in 0..len {
let value = unsafe { values.add(index) };
out.push(unsafe { borrow_value(value, index, error) }?);
}
Ok(out)
}