-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add serde-kcl library for working with KCL objects/values
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
1 parent
b2b62ec
commit 9697f04
Showing
8 changed files
with
811 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,6 +66,7 @@ members = [ | |
"kcl", | ||
"kcl-macros", | ||
"kcl-test-server", | ||
"serde-kcl", | ||
] | ||
|
||
[workspace.dependencies] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.