Skip to content

Commit

Permalink
Add serde-kcl library for working with KCL objects/values
Browse files Browse the repository at this point in the history
This defines:

 - serde_kcl::Value
 - serde_kcl::Object
 - serde_kcl::to_val (takes a Rust type and 'serializes' it into KCL object)

All similar to their equivalents in `serde_json`.

Part of #1130
  • Loading branch information
adamchalmers committed Aug 14, 2024
1 parent b2b62ec commit 9697f04
Show file tree
Hide file tree
Showing 8 changed files with 811 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/wasm-lib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/wasm-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ members = [
"kcl",
"kcl-macros",
"kcl-test-server",
"serde-kcl",
]

[workspace.dependencies]
Expand Down
18 changes: 18 additions & 0 deletions src/wasm-lib/serde-kcl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "serde-kcl"
description = "KittyCAD Language object model"
version = "0.1.0"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-app"
rust-version = "1.80"
license = "MIT"
authors = ["Jess Frazelle", "Adam Chalmers", "Jon Tran", "KittyCAD, Inc"]
keywords = ["kcl", "KittyCAD", "CAD"]

[dependencies]
ryu = "1.0"
serde = "1.0.207"
thiserror = "1.0.63"

[dev-dependencies]
serde = { version = "1.0.207", features = ["derive"]}
29 changes: 29 additions & 0 deletions src/wasm-lib/serde-kcl/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::{fmt::Display, num::TryFromIntError};

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
Message(String),
#[error("Number is too big")]
NumberTooBig,
#[error("You cannot use this as a key of a KCL object")]
InvalidKey,
}

impl From<TryFromIntError> for Error {
fn from(_: TryFromIntError) -> Self {
Self::NumberTooBig
}
}

impl serde::ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}

impl serde::de::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
44 changes: 44 additions & 0 deletions src/wasm-lib/serde-kcl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use serde::Serialize;

pub use crate::error::Error;
pub use crate::object::Object;
pub use crate::value::Value;

mod error;
mod object;
mod value;

pub fn to_value<T>(value: T) -> Result<Value, Error>
where
T: Serialize,
{
value.serialize(crate::value::ser::Serializer)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn structs_into_kcl_object() {
#[derive(serde::Serialize)]
struct Person {
name: String,
age: u8,
}

let adam = Person {
name: "Adam".to_owned(),
age: 32,
};
let val = to_value(&adam).expect("Serializing to KCL object should pass");
let obj = val.as_object().unwrap();
let expected = Object {
properties: std::collections::HashMap::from([
("name".to_owned(), Value::from("Adam".to_owned())),
("age".to_owned(), Value::from(32)),
]),
};
assert_eq!(obj.properties, expected.properties);
}
}
35 changes: 35 additions & 0 deletions src/wasm-lib/serde-kcl/src/object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::collections::HashMap;

use crate::Value;

/// A KCL object.
#[derive(Debug, Default, PartialEq)]
pub struct Object {
/// The object's properties.
pub properties: HashMap<String, Value>,
}

impl Object {
/// Create a new object with no properties.
pub fn new() -> Self {
Self::default()
}
/// How many properties does this object have?
pub fn len(&self) -> usize {
self.properties.len()
}
/// Add a new property to the object.
/// If the object already has a property with this name, overwrites it.
pub fn insert(&mut self, property: String, value: Value) {
self.properties.insert(property, value);
}
}

/// Given a list of (key, value) pairs, you can make a KCL object.
impl<const N: usize> From<[(String, Value); N]> for Object {
fn from(value: [(String, Value); N]) -> Self {
Self {
properties: HashMap::from(value),
}
}
}
84 changes: 84 additions & 0 deletions src/wasm-lib/serde-kcl/src/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use crate::Object;

pub(crate) mod ser;

#[derive(Debug, PartialEq)]
pub enum Value {
/// A value to use when the specific value isn't really important.
/// For example, this is the return type of functions that don't return
/// any other value.
///
/// Don't worry about it too much.
///
/// Kind of like 'null' in other languages, but it doesn't have the
/// connotation that nothing was missing. It probably means nothing was
/// required, not nothing was found.
Unit,
/// Either true or false.
Boolean(bool),
/// Text.
String(String),
/// Whole numbers (positive, negative or zero).
Integer(i64),
/// Numbers with a fractional part.
Float(f64),
/// A list of other values.
Array(Vec<Value>),
/// A set of properties. Each property has a name (aka "key") and a value.
Object(Object),
/// Binary data
Bytes(Vec<u8>),
}

macro_rules! impl_as {
($name:ident, $variant:ident, $return_type:ty) => {
pub fn $name(&self) -> Option<&$return_type> {
match self {
Self::$variant(x) => Some(x),
_ => None,
}
}
};
}

macro_rules! impl_from {
($variant:ident, $t:ty) => {
impl From<$t> for Value {
fn from(t: $t) -> Self {
Self::$variant(t.into())
}
}
};
}

impl Value {
impl_as!(as_boolean, Boolean, bool);
impl_as!(as_string, String, String);
impl_as!(as_integer, Integer, i64);
impl_as!(as_float, Float, f64);
impl_as!(as_array, Array, Vec<Value>);
impl_as!(as_object, Object, Object);
impl_as!(as_binary, Bytes, Vec<u8>);
pub fn as_unit(&self) -> Option<()> {
match self {
Self::Unit => Some(()),
_ => None,
}
}
}
impl_from!(String, String);
impl_from!(Boolean, bool);
impl_from!(Integer, i64);
impl_from!(Integer, i32);
impl_from!(Integer, u32);
impl_from!(Integer, u8);
impl_from!(Integer, i8);
impl_from!(Float, f64);
impl_from!(Float, f32);
impl_from!(Bytes, Vec<u8>);

impl From<()> for Value {
fn from(_: ()) -> Self {
Self::Unit
}
}
Loading

0 comments on commit 9697f04

Please sign in to comment.