binary_data_schema/
object.rs

1//! Implementation of the object schema
2//!
3//! In BDS object schemata are generally used to map between byte strings and JSON object.
4//! This should be used for most schemata modeling bytes that contain several fields.
5//!
6//! Every field within a byte string is modeled as one property of the object schema.
7//! The order of fields is defined by the `"position"` parameter in the property's schema.
8//!
9//! # Parameters
10//!
11//! | Key           | Type     | Default  | Comment |
12//! | ------------- | --------:| --------:| ------- |
13//! | `"position"`  |   `uint` |       NA | Position of the property in the encoded bytes |
14//! | `"jsonld:context"` | `string` | optional | A context in the sense of [JSON-LD] that is attached to the JSON resulting from decoding |
15//!
16//! ## Validation
17//!
18//! The positions of different properties should not be the same.
19//! Except they when they are defining a [merged bitfield].
20//!
21//! # Features
22//!
23//! ## Field Positions
24//!
25//! The BDS codec allows to transform JSON values into a byte string and reverse.
26//! To decode a byte string that is made of consecutive fields the describing object schema must define the order of fields.
27//! This is done via the `"position"` parameter in the properties of an object schema.
28//!
29//! `"position"`s of properties do not need to be continuous.
30//! They are just arranged in numeric order.
31//! However, they must not overlap.
32//! Except when several properties model a [merged bitfield].
33//!
34//! ### Example
35//!
36//! ```
37//! # use binary_data_schema::*;
38//! # use valico::json_schema;
39//! # use serde_json::{json, from_value};
40//! let schema = json!({
41//!     "type": "object",
42//!     "properties": {
43//!         "first": {
44//!             "type": "integer",
45//!             "length": 1,
46//!             "position": 10,
47//!         },
48//!         "third": {
49//!             "type": "integer",
50//!             "length": 1,
51//!             "position": 30,
52//!         },
53//!         "second": {
54//!             "type": "integer",
55//!             "length": 1,
56//!             "position": 20,
57//!         },
58//!     }
59//! });
60//!
61//! let mut scope = json_schema::Scope::new();
62//! let j_schema = scope.compile_and_return(schema.clone(), false)?;
63//! let schema = from_value::<DataSchema>(schema)?;
64//!
65//! let value = json!({ "third": 3, "first": 1, "second": 2});
66//! assert!(j_schema.validate(&value).is_valid());
67//! let mut encoded = Vec::new();
68//! schema.encode(&mut encoded, &value)?;
69//! let expected = [ 1, 2, 3 ];
70//! assert_eq!(&expected, encoded.as_slice());
71//!
72//! let mut encoded = std::io::Cursor::new(encoded);
73//! let back = schema.decode(&mut encoded)?;
74//! assert!(j_schema.validate(&back).is_valid());
75//! assert_eq!(back, value);
76//! # Ok::<(), anyhow::Error>(())
77//! ```
78//!
79//! ## Bitfields
80//!
81//! One of the more complex features of BDS is merged bitfields.
82//! It is common practice to merge several fields into one byte to safe space.
83//! For example, a single byte can contain up to 8 Boolean values.
84//! In BDS every data schema that is recognized as a bitfield can be merged.
85//! Recognized bitfield schemata are:
86//!
87//! - boolean schema
88//! - integer schema with `"bits"` and/or `"bitoffset" set
89//! - numeric schema with `"bits"` and/or `"bitoffset" set
90//!
91//! To merge bitfields they must be all properties of the same object schema with the **same** `"position"` and same number of bytes.
92//! Accordingly, it may be necessary to set the length of a boolean schema to more than one byte to merge it.
93//!
94//! ### Example
95//!
96//! ```
97//! # use binary_data_schema::*;
98//! # use valico::json_schema;
99//! # use serde_json::{json, from_value};
100//! let schema = json!({
101//!     "type": "object",
102//!     "properties": {
103//!         "num": {
104//!             "type": "number",
105//!             "position": 10,
106//!             "scale": 0.2,
107//!             "length": 1,
108//!             "bits": 4,
109//!             "bitoffset": 4,
110//!         },
111//!         "int": {
112//!             "type": "integer",
113//!             "position": 10,
114//!             "length": 1,
115//!             "bits": 3,
116//!             "bitoffset": 1,
117//!         },
118//!         "bool": {
119//!             "type": "boolean",
120//!             "position": 10,
121//!         }
122//!     }
123//! });
124//!
125//! let mut scope = json_schema::Scope::new();
126//! let j_schema = scope.compile_and_return(schema.clone(), false)?;
127//! let schema = from_value::<DataSchema>(schema)?;
128//!
129//! let value = json!({
130//!     "num": 2.4,
131//!     "int": 5,
132//!     "bool": false
133//! });
134//! assert!(j_schema.validate(&value).is_valid());
135//! let mut encoded = Vec::new();
136//! schema.encode(&mut encoded, &value)?;
137//! let num = 12 << 4;   // 2.4 / 0.2
138//! let int = 5 << 1;
139//! let bool_ = 0 << 0;  // false
140//! let expected: [u8; 1] = [num | int | bool_];
141//! assert_eq!(&expected, encoded.as_slice());
142//!
143//! let mut encoded = std::io::Cursor::new(encoded);
144//! let back = schema.decode(&mut encoded)?;
145//! assert!(j_schema.validate(&back).is_valid());
146//! // back is roughly the same but due to loss of precision because of floating point operations value is not exact.
147//! //assert_eq!(value, back);
148//! # Ok::<(), anyhow::Error>(())
149//! ```
150//!
151//! ## JSON-LD Context
152//!
153//! While JSON is a self-describing format sometimes even this is not enough, e.g. when also the semantics of the data should be conveyed.
154//! For these cases the BDS codec can add a [JSON-LD context] to the resulting JSON document, turning it into machine-readable [Linked Data].
155//! To do so the `"jsonld:context"` parameter is used like suggested in [JSON schema in RDF] draft.
156//! However, in contrast to the draft the value of `"jsonld:context"` is simply copied from the schema to `"@context"` in the result.
157//!
158//! For correct RDF interpretation of `"jsonld:context"` the JSON-LD context of the schema must declare the `jsonld` prefix.
159//! In BDS's own context (https://solid.ti.rw.fau.de/public/ns/bds/context.jsonld) this is done.
160//! Furthermore, this context allows to correctly interpret any binary data schema as RDF.
161//!
162//! ### Example
163//!
164//! ```
165//! # use binary_data_schema::*;
166//! # use valico::json_schema;
167//! # use serde_json::{json, from_value};
168//! let schema = json!({
169//!     "@context": "https://solid.ti.rw.fau.de/public/ns/bds/context.jsonld",
170//!     "type": "object",
171//!     "jsonld:context": "http://example.org/temperature/context.jsonld",
172//!     "properties": {
173//!         "temperature": {
174//!             "type": "integer",
175//!             "position": 10,
176//!             "length": 1,
177//!         }
178//!     }
179//! });
180//!
181//! let mut scope = json_schema::Scope::new();
182//! let j_schema = scope.compile_and_return(schema.clone(), false)?;
183//! let schema = from_value::<DataSchema>(schema)?;
184//!
185//! let encoded: [u8; 1] = [ 21 ];
186//! let mut encoded = std::io::Cursor::new(encoded);
187//! let back = schema.decode(&mut encoded)?;
188//! assert!(j_schema.validate(&back).is_valid());
189//! let expected = json!({
190//!     "@context": "http://example.org/temperature/context.jsonld",
191//!     "temperature": 21
192//! });
193//! assert_eq!(expected, back);
194//! # Ok::<(), anyhow::Error>(())
195//! ```
196//!
197//! [JSON-LD]: https://www.w3.org/TR/json-ld/#the-context
198//! [JSON-LD context]: https://www.w3.org/TR/json-ld/#the-context
199//! [merged bitfield]: #bitfields
200//! [Linked Data]: https://www.w3.org/DesignIssues/LinkedData.html
201//! [JSON schema in RDF]: https://www.w3.org/2019/wot/json-schema#defining-a-json-ld-context-for-data-instances
202
203use std::{collections::HashMap, convert::TryFrom, io};
204
205use byteorder::{ReadBytesExt, WriteBytesExt};
206use serde::{
207    de::{Deserializer, Error as DeError},
208    Deserialize,
209};
210use serde_json::Value;
211
212use crate::{
213    integer::{Bitfield, PlainInteger},
214    BooleanSchema, DataSchema, Decoder, Encoder, Error, InnerSchema, IntegerSchema, NumberSchema,
215    Result,
216};
217
218/// Errors validating an [ObjectSchema].
219#[derive(Debug, thiserror::Error)]
220pub enum ValidationError {
221    #[error("Can not join bitfields as there are none")]
222    NoBitfields,
223    #[error("Can not join bitfields with different number of bytes")]
224    NotSameBytes,
225    #[error("Can not join bitfields as they are overlapping")]
226    OverlappingBitfields,
227    #[error("The position {0} has been used multiple times in the object schema but only bitfields are allowed to share a position")]
228    InvalidPosition(usize),
229}
230
231/// Errors encoding a string with an [ObjectSchema].
232#[derive(Debug, thiserror::Error)]
233pub enum EncodingError {
234    #[error("The value '{value}' can not be encoded with an object schema")]
235    InvalidValue { value: String },
236    #[error("Writing to buffer failed: {0}")]
237    WriteFail(#[from] io::Error),
238    #[error("Expected the constant value {expected} but got {got}")]
239    InvalidConstValue { expected: String, got: String },
240    #[error("The field '{0}' is not present in the value to encode")]
241    MissingField(String),
242    #[error(transparent)]
243    Number(#[from] crate::number::EncodingError),
244    #[error(transparent)]
245    Integer(#[from] crate::integer::EncodingError),
246    #[error(transparent)]
247    Boolean(#[from] crate::boolean::EncodingError),
248    #[error("Encoding sub-schema failed: {0}")]
249    SubSchema(Box<Error>),
250}
251
252impl From<Error> for EncodingError {
253    fn from(e: Error) -> Self {
254        EncodingError::SubSchema(Box::new(e))
255    }
256}
257
258/// Errors decoding a string with an [ObjectSchema].
259#[derive(Debug, thiserror::Error)]
260pub enum DecodingError {
261    #[error("Reading encoded data failed: {0}")]
262    ReadFail(#[from] io::Error),
263    #[error(transparent)]
264    Integer(#[from] crate::integer::DecodingError),
265    #[error("Decoding sub-schema failed: {0}")]
266    SubSchema(Box<Error>),
267}
268
269impl DecodingError {
270    pub fn due_to_eof(&self) -> bool {
271        match &self {
272            DecodingError::ReadFail(e) => e.kind() == std::io::ErrorKind::UnexpectedEof,
273            DecodingError::Integer(e) => e.due_to_eof(),
274            DecodingError::SubSchema(e) => e.due_to_eof(),
275        }
276    }
277}
278
279impl From<Error> for DecodingError {
280    fn from(e: Error) -> Self {
281        DecodingError::SubSchema(Box::new(e))
282    }
283}
284
285/// A single property within an object schema.
286#[derive(Debug, Clone, Deserialize)]
287struct RawProperty {
288    #[serde(flatten)]
289    schema: DataSchema,
290    /// Position of the property within the object. Determines the layout of
291    /// binary serialization.
292    position: usize,
293}
294
295/// A set of bitfields that all write to the same bytes.
296#[derive(Debug, Clone)]
297struct JoinedBitfield {
298    bytes: usize,
299    fields: HashMap<String, DataSchema>,
300}
301
302#[derive(Debug, Clone)]
303enum PropertySchema {
304    Simple { name: String, schema: DataSchema },
305    Merged(JoinedBitfield),
306}
307
308#[derive(Debug, Clone)]
309struct Property {
310    position: usize,
311    schema: PropertySchema,
312}
313
314#[derive(Debug, Clone, Deserialize)]
315struct RawObject {
316    properties: HashMap<String, RawProperty>,
317    #[serde(rename = "jsonld:context")]
318    context: Option<Value>,
319}
320
321/// The object schema to describe structured data (further information on [the module's documentation](index.html)).
322///
323/// The position of a property within an object schema defines the order in
324/// which the properties are written to bytes.
325/// Property position do not have to be continuous but must be distinct. Except
326/// for bitfields in which case the same position signals that bitfields share
327/// the same bytes.
328///
329/// An instance of this type is guaranteed to be valid.
330#[derive(Debug, Clone)]
331pub struct ObjectSchema {
332    properties: Vec<Property>,
333    context: Option<Value>,
334}
335
336impl JoinedBitfield {
337    pub fn join(bfs: HashMap<String, DataSchema>) -> Result<Self, ValidationError> {
338        if bfs.values().any(|ds| !ds.is_bitfield()) {
339            return Err(ValidationError::NoBitfields);
340        }
341
342        let raw_bfs = bfs
343            .iter()
344            .map(|(name, ds)| {
345                let bf = ds.inner.bitfield().expect("ensured at beginning");
346                (name.as_str(), bf)
347            })
348            .collect::<HashMap<_, _>>();
349
350        let bytes = raw_bfs
351            .values()
352            .next()
353            .ok_or(ValidationError::NoBitfields)?
354            .bytes;
355
356        if raw_bfs.values().any(|bf| bf.bytes != bytes) {
357            return Err(ValidationError::NotSameBytes);
358        }
359
360        raw_bfs.values().try_fold(0u64, |state, bf| {
361            let mask = bf.mask();
362            if state & mask != 0 {
363                Err(ValidationError::OverlappingBitfields)
364            } else {
365                Ok(state | mask)
366            }
367        })?;
368
369        Ok(Self { bytes, fields: bfs })
370    }
371    fn raw_bfs(&self) -> impl Iterator<Item = (&'_ str, &'_ Bitfield)> {
372        self.fields.iter().map(|(name, ds)| {
373            let bf = ds.inner.bitfield().expect("ensured at constructor");
374            (name.as_str(), bf)
375        })
376    }
377    /// Integer schema to encode the value of all bitfields.
378    fn integer(&self) -> PlainInteger {
379        self.raw_bfs()
380            .map(|(_, bf)| bf)
381            .next()
382            .expect("Constuctor guarantees that there is at least one bitfield")
383            .integer()
384    }
385}
386
387impl Encoder for JoinedBitfield {
388    type Error = EncodingError;
389
390    fn encode<W>(&self, target: &mut W, value: &Value) -> Result<usize, Self::Error>
391    where
392        W: io::Write + WriteBytesExt,
393    {
394        let mut buffer = 0;
395        for (name, ds) in self.fields.iter() {
396            let value = match (value.get(name), ds.default_.as_ref()) {
397                (Some(val), Some(c)) if val == c => Ok(val),
398                (Some(val), Some(c)) => Err(EncodingError::InvalidConstValue {
399                    expected: c.to_string(),
400                    got: val.to_string(),
401                }),
402                (Some(val), None) => Ok(val),
403                (None, Some(c)) => Ok(c),
404                (None, None) => Err(EncodingError::MissingField(name.clone())),
405            }?;
406
407            let (bf, value) = match &ds.inner {
408                InnerSchema::Number(
409                    ns
410                    @
411                    NumberSchema::Integer {
412                        integer: IntegerSchema::Bitfield(_),
413                        ..
414                    },
415                ) => {
416                    let value = value.as_f64().ok_or_else(|| {
417                        crate::number::EncodingError::InvalidValue {
418                            value: value.to_string(),
419                        }
420                    })?;
421                    let value = ns.to_binary_value(value) as _;
422                    if let NumberSchema::Integer {
423                        integer: IntegerSchema::Bitfield(bf),
424                        ..
425                    } = ns
426                    {
427                        (bf, value)
428                    } else {
429                        unreachable!("ensured by match")
430                    }
431                }
432                InnerSchema::Integer(IntegerSchema::Bitfield(bf)) => {
433                    let value = value.as_u64().ok_or_else(|| {
434                        crate::integer::EncodingError::InvalidValue {
435                            value: value.to_string(),
436                        }
437                    })?;
438                    (bf, value)
439                }
440                InnerSchema::Boolean(BooleanSchema { bf }) => {
441                    let value = value.as_bool().ok_or_else(|| {
442                        crate::boolean::EncodingError::InvalidValue {
443                            value: value.to_string(),
444                        }
445                    })? as _;
446                    (bf, value)
447                }
448                _ => unreachable!("ensured at constructor"),
449            };
450            bf.write(value, &mut buffer);
451        }
452
453        let int = self.integer();
454        int.encode(target, &buffer.into()).map_err(Into::into)
455    }
456}
457
458impl Decoder for JoinedBitfield {
459    type Error = DecodingError;
460
461    fn decode<R>(&self, target: &mut R) -> Result<Value, Self::Error>
462    where
463        R: io::Read + ReadBytesExt,
464    {
465        let int = self.integer();
466        let int = int.decode(target)?.as_u64().expect("Is always u64");
467        let mut res = Value::default();
468        for (name, ds) in self.fields.iter() {
469            let bf = ds.inner.bitfield().expect("ensured at consturctor");
470            let value = bf.read(int);
471            res[name] = match &ds.inner {
472                InnerSchema::Number(ns) => ns.from_binary_value(value as _).into(),
473                InnerSchema::Boolean(_) => (value != 0).into(),
474                _ => value.into(),
475            };
476        }
477
478        Ok(res)
479    }
480}
481
482impl Encoder for PropertySchema {
483    type Error = EncodingError;
484
485    fn encode<W>(&self, target: &mut W, value: &Value) -> Result<usize, Self::Error>
486    where
487        W: io::Write + WriteBytesExt,
488    {
489        match self {
490            PropertySchema::Simple { name, schema } => {
491                let value = match (value.get(name), schema.default_.as_ref()) {
492                    (Some(val), Some(c)) if val == c => Ok(val),
493                    (Some(val), Some(c)) => Err(EncodingError::InvalidConstValue {
494                        expected: c.to_string(),
495                        got: val.to_string(),
496                    }),
497                    (Some(val), None) => Ok(val),
498                    (None, Some(c)) => Ok(c),
499                    (None, None) => Err(EncodingError::MissingField(name.clone())),
500                }?;
501                schema.encode(target, value).map_err(Into::into)
502            }
503            PropertySchema::Merged(schema) => schema.encode(target, value).map_err(Into::into),
504        }
505    }
506}
507
508impl PropertySchema {
509    /// Decodes values and adds them to the provided Json.
510    ///
511    /// This is contrary to the normal [Decoder] trait where new values are
512    /// created.
513    fn decode_into<R>(&self, target: &mut R, value: &mut Value) -> Result<(), DecodingError>
514    where
515        R: io::Read + ReadBytesExt,
516    {
517        match self {
518            PropertySchema::Simple { name, schema } => {
519                value[name] = schema.decode(target)?;
520            }
521            PropertySchema::Merged(schema) => {
522                let map = if let Value::Object(map) = schema.decode(target)? {
523                    map
524                } else {
525                    panic!("Should have always been a map");
526                };
527                for (name, bf_value) in map {
528                    value[name] = bf_value;
529                }
530            }
531        }
532
533        Ok(())
534    }
535}
536
537impl TryFrom<RawObject> for ObjectSchema {
538    type Error = ValidationError;
539
540    fn try_from(raw: RawObject) -> Result<Self, Self::Error> {
541        let mut ordered = HashMap::with_capacity(raw.properties.len());
542        raw.properties.into_iter().for_each(|(name, raw)| {
543            let bucket = ordered.entry(raw.position).or_insert_with(Vec::new);
544            bucket.push((name, raw.schema));
545        });
546        let mut properties = Vec::with_capacity(ordered.len());
547        for (position, mut vec) in ordered {
548            let prop = if vec.len() == 1 {
549                let (name, schema) = vec.pop().expect("Ensured by .len() == 1");
550                Property {
551                    position,
552                    schema: PropertySchema::Simple { name, schema },
553                }
554            } else {
555                let fields: Result<HashMap<_, _>, _> = vec
556                    .into_iter()
557                    .map(|(name, schema)| {
558                        if schema.is_bitfield() {
559                            Ok((name, schema))
560                        } else {
561                            Err(ValidationError::InvalidPosition(position))
562                        }
563                    })
564                    .collect();
565                let joined = JoinedBitfield::join(fields?)?;
566                Property {
567                    position,
568                    schema: PropertySchema::Merged(joined),
569                }
570            };
571            properties.push(prop);
572        }
573
574        properties.sort_by(|p1, p2| p1.position.cmp(&p2.position));
575        Ok(Self {
576            properties,
577            context: raw.context,
578        })
579    }
580}
581
582impl<'de> Deserialize<'de> for ObjectSchema {
583    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
584    where
585        D: Deserializer<'de>,
586    {
587        let raw = RawObject::deserialize(deserializer)?;
588        ObjectSchema::try_from(raw).map_err(D::Error::custom)
589    }
590}
591
592impl Encoder for ObjectSchema {
593    type Error = EncodingError;
594
595    fn encode<W>(&self, target: &mut W, value: &Value) -> Result<usize, Self::Error>
596    where
597        W: io::Write + WriteBytesExt,
598    {
599        let mut written = 0;
600        for p in self.properties.iter() {
601            written += p.schema.encode(target, &value)?;
602        }
603
604        Ok(written)
605    }
606}
607
608impl Decoder for ObjectSchema {
609    type Error = DecodingError;
610
611    fn decode<R>(&self, target: &mut R) -> Result<Value, Self::Error>
612    where
613        R: io::Read + ReadBytesExt,
614    {
615        let mut value = serde_json::json!({});
616        for p in self.properties.iter() {
617            p.schema.decode_into(target, &mut value)?;
618        }
619
620        if let Some(ctx) = self.context.as_ref() {
621            value["@context"] = ctx.clone();
622        }
623
624        Ok(value)
625    }
626}
627
628#[cfg(test)]
629mod test {
630    use super::*;
631    use crate::InnerSchema;
632    use anyhow::Result;
633    use serde_json::{from_value, json};
634
635    #[test]
636    fn xiaomi_thermometer() -> Result<()> {
637        let schema = json!({
638            "type": "object",
639            "properties": {
640                "temperature": {
641                    "type": "number",
642                    "position": 1,
643                    "length": 2,
644                    "scale": 0.01,
645                    "byteorder": "littleendian",
646                    "unit": "degree celcius"
647                },
648                "humidity": {
649                    "type": "integer",
650                    "position": 2,
651                    "length": 1,
652                    "unit": "percent"
653                },
654                "rest": {
655                    "type": "integer",
656                    "position": 5,
657                    "length": 2
658                }
659            }
660        });
661        let schema = from_value::<DataSchema>(schema)?;
662        assert!(matches!(
663            schema,
664            DataSchema {
665                inner: InnerSchema::Object(_),
666                ..
667            }
668        ));
669        let value = json!({
670            "temperature": 22.1,
671            "humidity": 57,
672            "rest": 0
673        });
674        let mut buffer = Vec::new();
675        assert_eq!(5, schema.encode(&mut buffer, &value)?);
676        let expected = [0xA2, 0x08, 0x39, 0, 0];
677        assert_eq!(&expected, buffer.as_slice());
678        let mut cursor = std::io::Cursor::new(buffer);
679
680        let returned = schema.decode(&mut cursor)?;
681        assert_eq!(value, returned);
682
683        Ok(())
684    }
685
686    #[test]
687    fn ruuvi_tag() -> Result<()> {
688        let schema = json!({
689            "type": "object",
690            "properties": {
691                "version": {
692                    "type": "integer",
693                    "length": 1,
694                    "default": 5,
695                    "position": 1,
696                    "description": "Version number of the protocol."
697                },
698                "temperature": {
699                    "@type": "env:RoomTemperature",
700                    "type": "number",
701                    "length": 2,
702                    "scale": 0.005,
703                    "position": 10,
704                    "unit": "degree celcius",
705                    "description": "Temperature of the air surrounding the RuuviTag."
706                },
707                "humidity": {
708                    "@type": "env:AirHumidity",
709                    "type": "number",
710                    "length": 2,
711                    "scale": 0.0025,
712                    "signed": false,
713                    "position": 20,
714                    "unit": "percent",
715                    "description": "Relative humidity of the air surrounding the RuuviTag."
716                },
717                "pressure": {
718                    "@type": "env:AtmosphericPressure",
719                    "type": "number",
720                    "length": 2,
721                    "offset": 50000,
722                    "signed": false,
723                    "position": 30,
724                    "unit": "Pa",
725                    "description": "Atmospheric pressure on the RuuviTag."
726                },
727                "acceleration": {
728                    "type": "object",
729                    "position": 40,
730                    "description": "3D accereration of the RuuviTag.",
731                    "properties": {
732                        "x": {
733                            "type": "number",
734                            "length": 2,
735                            "scale": 0.001,
736                            "unit": "G-force",
737                            "position": 10,
738                            "desription": "Acceleration in x-axis."
739                        },
740                        "y": {
741                            "type": "number",
742                            "length": 2,
743                            "scale": 0.001,
744                            "unit": "G-force",
745                            "position": 20,
746                            "desription": "Acceleration in y-axis."
747                        },
748                        "z": {
749                            "type": "number",
750                            "length": 2,
751                            "scale": 0.001,
752                            "unit": "G-force",
753                            "position": 30,
754                            "desription": "Acceleration in z-axis."
755                        }
756                    }
757                },
758                "battery": {
759                    "type": "number",
760                    "offset": 1.6,
761                    "scale": 0.001,
762                    "length": 2,
763                    "bits": 11,
764                    "bitoffset": 5,
765                    "position": 50,
766                    "unit": "volts",
767                    "description": "Voltage of the battery powering the RuuviTag."
768                },
769                "txPower": {
770                    "type": "number",
771                    "offset": -40,
772                    "scale": 2,
773                    "length": 2,
774                    "bits": 5,
775                    "bitoffset": 0,
776                    "position": 50,
777                    "unit": "dBm",
778                    "description": "Transmission power in 1m distance."
779                },
780                "moveCnt": {
781                    "type": "integer",
782                    "length": 1,
783                    "signed": false,
784                    "position": 60,
785                    "description": "Number of movements (derived from accelerometer)."
786                },
787                "idx": {
788                    "type": "integer",
789                    "length": 2,
790                    "signed": false,
791                    "position": 70,
792                    "description": "Measurement sequence number. Can be used to de-duplicate data."
793                },
794                "mac": {
795                    "type": "string",
796                    "format": "binary",
797                    "minLength": 12,
798                    "maxLength": 12,
799                    "position": 80,
800                    "description": "MAC address of the RuuviTag."
801                }
802            }
803        });
804        let _schema = from_value::<DataSchema>(schema)?;
805
806        Ok(())
807    }
808
809    #[test]
810    fn merge_bitfields() -> Result<()> {
811        let schema = json!({
812            "type": "object",
813            "properties": {
814                "battery": {
815                    "type": "number",
816                    "offset": 1.6,
817                    "scale": 0.001,
818                    "length": 2,
819                    "bits": 11,
820                    "bitoffset": 5,
821                    "position": 50,
822                    "unit": "volts",
823                    "description": "Voltage of the battery powering the RuuviTag."
824                },
825                "txPower": {
826                    "type": "number",
827                    "offset": -40,
828                    "scale": 2,
829                    "length": 2,
830                    "bits": 5,
831                    "bitoffset": 0,
832                    "position": 50,
833                    "unit": "dBm",
834                    "description": "Transmission power in 1m distance."
835                }
836            }
837        });
838        let schema = from_value::<DataSchema>(schema)?;
839        println!("schema:\n{:#?}", schema);
840        assert!(matches!(
841            schema,
842            DataSchema {
843                inner: InnerSchema::Object(_),
844                ..
845            }
846        ));
847
848        let value = json!({
849            "battery": 3.0,
850            "txPower": 4,
851        });
852        let mut buffer = Vec::new();
853        assert_eq!(2, schema.encode(&mut buffer, &value)?);
854        let _battery = 0b101_0111_1000; // 1400 = 3.0 V
855        let _tx_power = 0b1_0110; // 22 = +4 dBm
856        let expected: [u8; 2] = [0b1010_1111, 0b0001_0110];
857        assert_eq!(&expected, buffer.as_slice());
858        let mut cursor = std::io::Cursor::new(buffer);
859
860        let _returned = schema.decode(&mut cursor)?;
861        // returned is roughly the same but due to loss of precision due to floating point operations value is not exact.
862        //assert_eq!(value, returned);
863
864        Ok(())
865    }
866    #[test]
867    fn merge_different_bitfields() -> Result<()> {
868        let schema = json!({
869            "type": "object",
870            "properties": {
871                "num": {
872                    "type": "number",
873                    "position": 10,
874                    "scale": 0.2,
875                    "length": 1,
876                    "bits": 4,
877                    "bitoffset": 4,
878                },
879                "int": {
880                    "type": "integer",
881                    "position": 10,
882                    "length": 1,
883                    "bits": 3,
884                    "bitoffset": 1,
885                },
886                "bool": {
887                    "type": "boolean",
888                    "position": 10,
889                }
890            }
891        });
892        let schema = from_value::<DataSchema>(schema)?;
893        println!("schema:\n{:#?}", schema);
894        assert!(matches!(
895            schema,
896            DataSchema {
897                inner: InnerSchema::Object(_),
898                ..
899            }
900        ));
901
902        let value = json!({
903            "num": 2.4,
904            "int": 5,
905            "bool": false
906        });
907        let mut buffer = Vec::new();
908        assert_eq!(1, schema.encode(&mut buffer, &value)?);
909        let num = 12 << 4;
910        let int = 5 << 1;
911        let bool_ = 0 << 0;
912        let expected: [u8; 1] = [num | int | bool_];
913        assert_eq!(&expected, buffer.as_slice());
914        let mut cursor = std::io::Cursor::new(buffer);
915
916        let _returned = schema.decode(&mut cursor)?;
917        // returned is roughly the same but due to loss of precision due to floating point operations value is not exact.
918        // assert_eq!(value, returned);
919
920        Ok(())
921    }
922    #[test]
923    fn jsonld_context() -> Result<()> {
924        let schema = json!({
925            "type": "object",
926            "properties": {
927                "test": {
928                    "type": "integer",
929                    "length": 1,
930                    "position": 10
931                }
932            },
933            "jsonld:context": "http://example.org/context.jsonld"
934        });
935        let schema = from_value::<DataSchema>(schema)?;
936        assert!(matches!(
937            schema,
938            DataSchema {
939                inner: InnerSchema::Object(ObjectSchema {
940                    context: Some(_),
941                    ..
942                }),
943                ..
944            }
945        ));
946
947        let buffer = vec![0x10];
948        let mut cursor = std::io::Cursor::new(buffer);
949
950        let returned = schema.decode(&mut cursor)?;
951        let expected = json!({
952            "@context": "http://example.org/context.jsonld",
953            "test": 16
954        });
955        assert_eq!(returned, expected);
956
957        Ok(())
958    }
959}