Skip to main content

spvirit_client/
put_encode.rs

1use serde_json::Value;
2
3use spvirit_codec::spvirit_encode::encode_size_pva;
4use spvirit_codec::spvd_decode::{FieldDesc, FieldType, StructureDesc, TypeCode};
5use spvirit_codec::spvd_encode::encode_structure_desc;
6
7pub fn encode_put_payload(
8    desc: &StructureDesc,
9    input: &Value,
10    is_be: bool,
11) -> Result<Vec<u8>, String> {
12    let root = normalize_root(desc, input)?;
13    let mut bitset: Vec<u8> = Vec::new();
14    let mut data: Vec<u8> = Vec::new();
15    let mut bit_offset = 1usize;
16    encode_structure_partial(desc, &root, &mut bitset, &mut bit_offset, &mut data, is_be)?;
17    if bitset.is_empty() {
18        return Err("no fields selected for PUT".to_string());
19    }
20
21    let mut out = Vec::new();
22    out.extend_from_slice(&encode_size_pva(bitset.len(), is_be));
23    out.extend_from_slice(&bitset);
24    out.extend_from_slice(&data);
25    Ok(out)
26}
27
28fn normalize_root(
29    desc: &StructureDesc,
30    input: &Value,
31) -> Result<serde_json::Map<String, Value>, String> {
32    if let Some(obj) = input.as_object() {
33        return Ok(obj.clone());
34    }
35    if desc.fields.iter().any(|f| f.name == "value") {
36        let mut map = serde_json::Map::new();
37        map.insert("value".to_string(), input.clone());
38        return Ok(map);
39    }
40    Err("non-object input requires a 'value' field in structure".to_string())
41}
42
43fn set_bit(bitset: &mut Vec<u8>, bit: usize) {
44    let idx = bit / 8;
45    let mask = 1u8 << (bit % 8);
46    if bitset.len() <= idx {
47        bitset.resize(idx + 1, 0);
48    }
49    bitset[idx] |= mask;
50}
51
52fn count_structure_fields(desc: &StructureDesc) -> usize {
53    let mut count = 0;
54    for field in &desc.fields {
55        count += 1;
56        if let FieldType::Structure(nested) = &field.field_type {
57            count += count_structure_fields(nested);
58        }
59    }
60    count
61}
62
63fn encode_structure_partial(
64    desc: &StructureDesc,
65    values: &serde_json::Map<String, Value>,
66    bitset: &mut Vec<u8>,
67    bit_offset: &mut usize,
68    data: &mut Vec<u8>,
69    is_be: bool,
70) -> Result<(), String> {
71    for key in values.keys() {
72        if !desc.fields.iter().any(|f| f.name == *key) {
73            return Err(format!("unknown field '{}' in input", key));
74        }
75    }
76    for field in &desc.fields {
77        let current_bit = *bit_offset;
78        *bit_offset += 1;
79
80        match &field.field_type {
81            FieldType::Structure(nested) => {
82                if let Some(val) = values.get(&field.name) {
83                    if let Some(obj) = val.as_object() {
84                        if obj.is_empty() {
85                            return Err(format!("field '{}' object has no entries", field.name));
86                        }
87                        encode_structure_partial(nested, obj, bitset, bit_offset, data, is_be)?;
88                    } else {
89                        return Err(format!("field '{}' expects object", field.name));
90                    }
91                } else {
92                    let child_count = count_structure_fields(nested);
93                    *bit_offset += child_count;
94                }
95            }
96            _ => {
97                if let Some(val) = values.get(&field.name) {
98                    set_bit(bitset, current_bit);
99                    encode_field_value(&field.field_type, val, data, is_be, &field.name)?;
100                }
101            }
102        }
103    }
104    Ok(())
105}
106
107fn encode_structure_full(
108    desc: &StructureDesc,
109    values: &serde_json::Map<String, Value>,
110    data: &mut Vec<u8>,
111    is_be: bool,
112) -> Result<(), String> {
113    for field in &desc.fields {
114        let val = values
115            .get(&field.name)
116            .ok_or_else(|| format!("missing field '{}' for full structure encoding", field.name))?;
117        match &field.field_type {
118            FieldType::Structure(nested) => {
119                let obj = val
120                    .as_object()
121                    .ok_or_else(|| format!("field '{}' expects object", field.name))?;
122                encode_structure_full(nested, obj, data, is_be)?;
123            }
124            _ => {
125                encode_field_value(&field.field_type, val, data, is_be, &field.name)?;
126            }
127        }
128    }
129    Ok(())
130}
131
132fn encode_field_value(
133    field_type: &FieldType,
134    val: &Value,
135    data: &mut Vec<u8>,
136    is_be: bool,
137    field_name: &str,
138) -> Result<(), String> {
139    match field_type {
140        FieldType::Scalar(tc) => encode_scalar_value(*tc, val, data, is_be, field_name),
141        FieldType::ScalarArray(tc) => encode_scalar_array(*tc, val, data, is_be, field_name),
142        FieldType::String | FieldType::BoundedString(_) => {
143            encode_string_value(val, data, is_be, field_name)
144        }
145        FieldType::StringArray => encode_string_array(val, data, is_be, field_name),
146        FieldType::Structure(_) => Err(format!(
147            "field '{}' expects object (nested structure)",
148            field_name
149        )),
150        FieldType::StructureArray(nested) => {
151            encode_structure_array(nested, val, data, is_be, field_name)
152        }
153        FieldType::Union(fields) => encode_union(fields, val, data, is_be, field_name),
154        FieldType::UnionArray(fields) => encode_union_array(fields, val, data, is_be, field_name),
155        FieldType::Variant => encode_variant(val, data, is_be, field_name),
156        FieldType::VariantArray => encode_variant_array(val, data, is_be, field_name),
157    }
158}
159
160fn encode_string_value(
161    val: &Value,
162    data: &mut Vec<u8>,
163    is_be: bool,
164    field_name: &str,
165) -> Result<(), String> {
166    let s = match val {
167        Value::String(s) => s.clone(),
168        _ => return Err(format!("field '{}' expects string", field_name)),
169    };
170    data.extend_from_slice(&encode_size_pva(s.len(), is_be));
171    data.extend_from_slice(s.as_bytes());
172    Ok(())
173}
174
175fn encode_scalar_value(
176    tc: TypeCode,
177    val: &Value,
178    data: &mut Vec<u8>,
179    is_be: bool,
180    field_name: &str,
181) -> Result<(), String> {
182    match tc {
183        TypeCode::Boolean => {
184            let b = match val {
185                Value::Bool(v) => *v,
186                Value::Number(n) => {
187                    let v = n
188                        .as_i64()
189                        .ok_or_else(|| format!("field '{}' expects boolean", field_name))?;
190                    v != 0
191                }
192                _ => return Err(format!("field '{}' expects boolean", field_name)),
193            };
194            data.push(if b { 1 } else { 0 });
195            Ok(())
196        }
197        TypeCode::Int8 => {
198            let v = json_to_int(val, field_name)?;
199            ensure_range_i64(v, i8::MIN as i64, i8::MAX as i64, field_name)?;
200            data.push(v as i8 as u8);
201            Ok(())
202        }
203        TypeCode::Int16 => {
204            let v = json_to_int(val, field_name)?;
205            ensure_range_i64(v, i16::MIN as i64, i16::MAX as i64, field_name)?;
206            let v = v as i16;
207            push_i16(data, v, is_be);
208            Ok(())
209        }
210        TypeCode::Int32 => {
211            let v = json_to_int(val, field_name)?;
212            ensure_range_i64(v, i32::MIN as i64, i32::MAX as i64, field_name)?;
213            let v = v as i32;
214            push_i32(data, v, is_be);
215            Ok(())
216        }
217        TypeCode::Int64 => {
218            let v = json_to_int(val, field_name)?;
219            push_i64(data, v, is_be);
220            Ok(())
221        }
222        TypeCode::UInt8 => {
223            let v = json_to_uint(val, field_name)?;
224            ensure_range_u64(v, u8::MIN as u64, u8::MAX as u64, field_name)?;
225            let v = v as u8;
226            data.push(v);
227            Ok(())
228        }
229        TypeCode::UInt16 => {
230            let v = json_to_uint(val, field_name)?;
231            ensure_range_u64(v, u16::MIN as u64, u16::MAX as u64, field_name)?;
232            let v = v as u16;
233            push_u16(data, v, is_be);
234            Ok(())
235        }
236        TypeCode::UInt32 => {
237            let v = json_to_uint(val, field_name)?;
238            ensure_range_u64(v, u32::MIN as u64, u32::MAX as u64, field_name)?;
239            let v = v as u32;
240            push_u32(data, v, is_be);
241            Ok(())
242        }
243        TypeCode::UInt64 => {
244            let v = json_to_uint(val, field_name)?;
245            push_u64(data, v, is_be);
246            Ok(())
247        }
248        TypeCode::Float32 => {
249            let v = json_to_float(val, field_name)? as f32;
250            push_f32(data, v, is_be);
251            Ok(())
252        }
253        TypeCode::Float64 => {
254            let v = json_to_float(val, field_name)?;
255            push_f64(data, v, is_be);
256            Ok(())
257        }
258        TypeCode::String => encode_string_value(val, data, is_be, field_name),
259        _ => Err(format!(
260            "field '{}' uses unsupported scalar type",
261            field_name
262        )),
263    }
264}
265
266fn encode_scalar_array(
267    tc: TypeCode,
268    val: &Value,
269    data: &mut Vec<u8>,
270    is_be: bool,
271    field_name: &str,
272) -> Result<(), String> {
273    let items = val
274        .as_array()
275        .ok_or_else(|| format!("field '{}' expects array", field_name))?;
276    data.extend_from_slice(&encode_size_pva(items.len(), is_be));
277    for item in items {
278        encode_scalar_value(tc, item, data, is_be, field_name)?;
279    }
280    Ok(())
281}
282
283fn encode_string_array(
284    val: &Value,
285    data: &mut Vec<u8>,
286    is_be: bool,
287    field_name: &str,
288) -> Result<(), String> {
289    let items = val
290        .as_array()
291        .ok_or_else(|| format!("field '{}' expects array", field_name))?;
292    data.extend_from_slice(&encode_size_pva(items.len(), is_be));
293    for item in items {
294        encode_string_value(item, data, is_be, field_name)?;
295    }
296    Ok(())
297}
298
299fn encode_structure_array(
300    desc: &StructureDesc,
301    val: &Value,
302    data: &mut Vec<u8>,
303    is_be: bool,
304    field_name: &str,
305) -> Result<(), String> {
306    let items = val
307        .as_array()
308        .ok_or_else(|| format!("field '{}' expects array", field_name))?;
309    data.extend_from_slice(&encode_size_pva(items.len(), is_be));
310    for item in items {
311        let obj = item
312            .as_object()
313            .ok_or_else(|| format!("field '{}' expects array of objects", field_name))?;
314        encode_structure_full(desc, obj, data, is_be)?;
315    }
316    Ok(())
317}
318
319fn encode_union(
320    fields: &[FieldDesc],
321    val: &Value,
322    data: &mut Vec<u8>,
323    is_be: bool,
324    field_name: &str,
325) -> Result<(), String> {
326    let obj = val
327        .as_object()
328        .ok_or_else(|| format!("field '{}' expects object for union", field_name))?;
329    if obj.len() != 1 {
330        return Err(format!(
331            "field '{}' union expects exactly one selected member",
332            field_name
333        ));
334    }
335    let (selected_name, selected_val) = obj.iter().next().expect("len checked");
336    let Some((index, field_desc)) = fields
337        .iter()
338        .enumerate()
339        .find(|(_, f)| f.name == *selected_name)
340    else {
341        return Err(format!(
342            "field '{}' union member '{}' is unknown",
343            field_name, selected_name
344        ));
345    };
346    data.extend_from_slice(&encode_size_pva(index, is_be));
347    encode_field_value(
348        &field_desc.field_type,
349        selected_val,
350        data,
351        is_be,
352        selected_name,
353    )
354}
355
356fn encode_union_array(
357    fields: &[FieldDesc],
358    val: &Value,
359    data: &mut Vec<u8>,
360    is_be: bool,
361    field_name: &str,
362) -> Result<(), String> {
363    let items = val
364        .as_array()
365        .ok_or_else(|| format!("field '{}' expects array for union[]", field_name))?;
366    data.extend_from_slice(&encode_size_pva(items.len(), is_be));
367    for item in items {
368        encode_union(fields, item, data, is_be, field_name)?;
369    }
370    Ok(())
371}
372
373fn encode_variant(
374    val: &Value,
375    data: &mut Vec<u8>,
376    is_be: bool,
377    field_name: &str,
378) -> Result<(), String> {
379    match val {
380        Value::Null => {
381            data.push(0xFF);
382            Ok(())
383        }
384        Value::Bool(_) => {
385            data.push(TypeCode::Boolean as u8);
386            encode_scalar_value(TypeCode::Boolean, val, data, is_be, field_name)
387        }
388        Value::Number(n) => {
389            if n.as_i64().is_some() || n.as_u64().is_some() {
390                data.push(TypeCode::Int32 as u8);
391                encode_scalar_value(TypeCode::Int32, val, data, is_be, field_name)
392            } else {
393                data.push(TypeCode::Float64 as u8);
394                encode_scalar_value(TypeCode::Float64, val, data, is_be, field_name)
395            }
396        }
397        Value::String(_) => {
398            data.push(TypeCode::String as u8);
399            encode_string_value(val, data, is_be, field_name)
400        }
401        Value::Array(arr) => {
402            if arr.is_empty() {
403                data.push((TypeCode::Float64 as u8) | 0x08);
404                data.extend_from_slice(&encode_size_pva(0, is_be));
405                return Ok(());
406            }
407            if arr.iter().all(Value::is_string) {
408                data.push(0x68); // string[]
409                return encode_string_array(val, data, is_be, field_name);
410            }
411            data.push((TypeCode::Float64 as u8) | 0x08);
412            encode_scalar_array(TypeCode::Float64, val, data, is_be, field_name)
413        }
414        Value::Object(map) => {
415            let mut desc_fields = Vec::new();
416            for key in map.keys() {
417                desc_fields.push(FieldDesc {
418                    name: key.clone(),
419                    field_type: FieldType::String,
420                });
421            }
422            let desc = StructureDesc {
423                struct_id: None,
424                fields: desc_fields,
425            };
426            data.push(0x80);
427            data.extend_from_slice(&encode_structure_desc(&desc, is_be));
428            for key in map.keys() {
429                let v = map.get(key).expect("iter key");
430                let s = match v {
431                    Value::String(s) => s.clone(),
432                    Value::Number(n) => n.to_string(),
433                    Value::Bool(b) => {
434                        if *b {
435                            "true".to_string()
436                        } else {
437                            "false".to_string()
438                        }
439                    }
440                    Value::Null => String::new(),
441                    _ => v.to_string(),
442                };
443                data.extend_from_slice(&encode_size_pva(s.len(), is_be));
444                data.extend_from_slice(s.as_bytes());
445            }
446            Ok(())
447        }
448    }
449}
450
451fn encode_variant_array(
452    val: &Value,
453    data: &mut Vec<u8>,
454    is_be: bool,
455    field_name: &str,
456) -> Result<(), String> {
457    let items = val
458        .as_array()
459        .ok_or_else(|| format!("field '{}' expects array for any[]", field_name))?;
460    data.extend_from_slice(&encode_size_pva(items.len(), is_be));
461    for item in items {
462        encode_variant(item, data, is_be, field_name)?;
463    }
464    Ok(())
465}
466
467fn json_to_int(val: &Value, field_name: &str) -> Result<i64, String> {
468    match val {
469        Value::Number(n) => {
470            if let Some(i) = n.as_i64() {
471                Ok(i)
472            } else if let Some(u) = n.as_u64() {
473                i64::try_from(u).map_err(|_| format!("field '{}' out of range", field_name))
474            } else if let Some(f) = n.as_f64() {
475                if (f.fract()).abs() > f64::EPSILON {
476                    Err(format!("field '{}' expects integer, got {}", field_name, f))
477                } else {
478                    Ok(f as i64)
479                }
480            } else {
481                Err(format!("field '{}' expects integer", field_name))
482            }
483        }
484        Value::String(s) => s
485            .parse::<i64>()
486            .map_err(|_| format!("field '{}' expects integer", field_name)),
487        _ => Err(format!("field '{}' expects integer", field_name)),
488    }
489}
490
491fn json_to_uint(val: &Value, field_name: &str) -> Result<u64, String> {
492    match val {
493        Value::Number(n) => {
494            if let Some(u) = n.as_u64() {
495                Ok(u)
496            } else if let Some(i) = n.as_i64() {
497                if i < 0 {
498                    Err(format!("field '{}' expects unsigned integer", field_name))
499                } else {
500                    Ok(i as u64)
501                }
502            } else if let Some(f) = n.as_f64() {
503                if (f.fract()).abs() > f64::EPSILON || f < 0.0 {
504                    Err(format!("field '{}' expects unsigned integer", field_name))
505                } else {
506                    Ok(f as u64)
507                }
508            } else {
509                Err(format!("field '{}' expects unsigned integer", field_name))
510            }
511        }
512        Value::String(s) => s
513            .parse::<u64>()
514            .map_err(|_| format!("field '{}' expects unsigned integer", field_name)),
515        _ => Err(format!("field '{}' expects unsigned integer", field_name)),
516    }
517}
518
519fn json_to_float(val: &Value, field_name: &str) -> Result<f64, String> {
520    match val {
521        Value::Number(n) => n
522            .as_f64()
523            .ok_or_else(|| format!("field '{}' expects float", field_name)),
524        Value::String(s) => s
525            .parse::<f64>()
526            .map_err(|_| format!("field '{}' expects float", field_name)),
527        _ => Err(format!("field '{}' expects float", field_name)),
528    }
529}
530
531fn ensure_range_i64(value: i64, min: i64, max: i64, field_name: &str) -> Result<(), String> {
532    if value < min || value > max {
533        Err(format!("field '{}' out of range", field_name))
534    } else {
535        Ok(())
536    }
537}
538
539fn ensure_range_u64(value: u64, min: u64, max: u64, field_name: &str) -> Result<(), String> {
540    if value < min || value > max {
541        Err(format!("field '{}' out of range", field_name))
542    } else {
543        Ok(())
544    }
545}
546
547fn push_i16(data: &mut Vec<u8>, v: i16, is_be: bool) {
548    let bytes = if is_be {
549        v.to_be_bytes()
550    } else {
551        v.to_le_bytes()
552    };
553    data.extend_from_slice(&bytes);
554}
555
556fn push_i32(data: &mut Vec<u8>, v: i32, is_be: bool) {
557    let bytes = if is_be {
558        v.to_be_bytes()
559    } else {
560        v.to_le_bytes()
561    };
562    data.extend_from_slice(&bytes);
563}
564
565fn push_i64(data: &mut Vec<u8>, v: i64, is_be: bool) {
566    let bytes = if is_be {
567        v.to_be_bytes()
568    } else {
569        v.to_le_bytes()
570    };
571    data.extend_from_slice(&bytes);
572}
573
574fn push_u16(data: &mut Vec<u8>, v: u16, is_be: bool) {
575    let bytes = if is_be {
576        v.to_be_bytes()
577    } else {
578        v.to_le_bytes()
579    };
580    data.extend_from_slice(&bytes);
581}
582
583fn push_u32(data: &mut Vec<u8>, v: u32, is_be: bool) {
584    let bytes = if is_be {
585        v.to_be_bytes()
586    } else {
587        v.to_le_bytes()
588    };
589    data.extend_from_slice(&bytes);
590}
591
592fn push_u64(data: &mut Vec<u8>, v: u64, is_be: bool) {
593    let bytes = if is_be {
594        v.to_be_bytes()
595    } else {
596        v.to_le_bytes()
597    };
598    data.extend_from_slice(&bytes);
599}
600
601fn push_f32(data: &mut Vec<u8>, v: f32, is_be: bool) {
602    let bytes = if is_be {
603        v.to_be_bytes()
604    } else {
605        v.to_le_bytes()
606    };
607    data.extend_from_slice(&bytes);
608}
609
610fn push_f64(data: &mut Vec<u8>, v: f64, is_be: bool) {
611    let bytes = if is_be {
612        v.to_be_bytes()
613    } else {
614        v.to_le_bytes()
615    };
616    data.extend_from_slice(&bytes);
617}
618
619#[cfg(test)]
620mod tests {
621    use super::*;
622    use spvirit_codec::spvd_decode::{FieldDesc, FieldType, StructureDesc, TypeCode};
623
624    #[test]
625    fn encode_put_scalar_value_bitset() {
626        let desc = StructureDesc {
627            struct_id: None,
628            fields: vec![FieldDesc {
629                name: "value".to_string(),
630                field_type: FieldType::Scalar(TypeCode::Int32),
631            }],
632        };
633        let input = serde_json::json!(1234);
634        let payload = encode_put_payload(&desc, &input, false).expect("payload");
635        assert_eq!(payload[0], 0x01); // bitset size
636        assert_eq!(payload[1], 0x02); // bit 1 set
637        let val_bytes = &payload[2..6];
638        assert_eq!(val_bytes, &1234i32.to_le_bytes());
639    }
640
641    #[test]
642    fn encode_put_nested_partial() {
643        let nested = StructureDesc {
644            struct_id: None,
645            fields: vec![FieldDesc {
646                name: "unit".to_string(),
647                field_type: FieldType::String,
648            }],
649        };
650        let desc = StructureDesc {
651            struct_id: None,
652            fields: vec![
653                FieldDesc {
654                    name: "value".to_string(),
655                    field_type: FieldType::Scalar(TypeCode::Float64),
656                },
657                FieldDesc {
658                    name: "meta".to_string(),
659                    field_type: FieldType::Structure(nested),
660                },
661            ],
662        };
663        let input = serde_json::json!({"value": 1.5, "meta": {"unit": "A"}});
664        let payload = encode_put_payload(&desc, &input, false).expect("payload");
665        assert_eq!(payload[0], 0x01);
666        assert_eq!(payload[1], 0x0A); // bits 1 and 3
667        let mut expected = Vec::new();
668        expected.extend_from_slice(&1.5f64.to_le_bytes());
669        expected.extend_from_slice(&encode_size_pva(1, false));
670        expected.extend_from_slice(b"A");
671        assert_eq!(&payload[2..], expected.as_slice());
672    }
673}