1use std::sync::{Arc, Mutex};
208
209mod format;
210
211const DEFAULT_BATCH_SIZE: usize = 32;
212
213use htsvcf_core as core;
214use htsvcf_core::variant::FormatValue;
215use napi::bindgen_prelude::*;
216use napi::{sys, Env};
217use napi_derive::napi;
218
219#[napi(object)]
228pub struct Genotype {
229 pub alleles: Vec<Option<i32>>,
231 pub phase: Vec<bool>,
234}
235
236impl From<core::Genotype> for Genotype {
237 fn from(g: core::Genotype) -> Self {
238 Genotype {
239 alleles: g.alleles,
240 phase: g.phase,
241 }
242 }
243}
244
245impl From<Genotype> for core::Genotype {
246 fn from(g: Genotype) -> Self {
247 core::Genotype {
248 alleles: g.alleles,
249 phase: g.phase,
250 }
251 }
252}
253
254#[napi(object)]
255pub struct ReaderOptions {}
256
257#[napi(object)]
258pub struct WriterOptions {
259 pub format: Option<String>,
260 pub uncompressed: Option<bool>,
261 pub threads: Option<u32>,
262}
263
264#[napi]
265pub struct Reader {
266 inner: Arc<Mutex<Option<core::Reader>>>,
267 header: Arc<core::Header>,
268 header_ref: Reference<Header>,
276}
277
278#[napi]
279impl Reader {
280 #[napi(constructor)]
281 pub fn new(env: Env, path: String, _opts: Option<ReaderOptions>) -> napi::Result<Self> {
282 let reader = core::open_reader(&path).map_err(|e| {
283 Error::new(
284 Status::GenericFailure,
285 format!("failed to open {path}: {e}"),
286 )
287 })?;
288
289 let header = Arc::new(unsafe { core::Header::new(reader.header_ptr()) });
290 let header_ref = Header::into_reference(
291 Header {
292 inner: header.clone(),
293 },
294 env,
295 )?;
296
297 Ok(Self {
298 inner: Arc::new(Mutex::new(Some(reader))),
299 header,
300 header_ref,
301 })
302 }
303
304 #[napi(getter)]
305 pub fn header(&self, env: Env) -> napi::Result<Reference<Header>> {
306 self.header_ref.clone(env)
307 }
308
309 #[napi(js_name = "hasIndex")]
310 pub fn has_index(&self) -> bool {
311 self.inner
312 .lock()
313 .ok()
314 .and_then(|g| g.as_ref().map(|r| r.has_index()))
315 .unwrap_or(false)
316 }
317
318 #[napi]
319 pub fn query(
320 &self,
321 region_or_chrom: String,
322 start0: Option<u32>,
323 end0: Option<u32>,
324 ) -> AsyncTask<QueryTask> {
325 AsyncTask::new(QueryTask {
326 inner: self.inner.clone(),
327 region_or_chrom,
328 start0,
329 end0,
330 })
331 }
332
333 #[napi]
334 pub fn next(&self) -> AsyncTask<NextTask> {
335 AsyncTask::new(NextTask {
336 inner: self.inner.clone(),
337 header: self.header.clone(),
338 })
339 }
340
341 #[napi(js_name = "nextBatchAsync")]
346 pub fn next_batch_async(&self, size: Option<u32>) -> napi::Result<AsyncTask<NextBatchTask>> {
347 let batch_size = size.map(|s| s as usize).unwrap_or(DEFAULT_BATCH_SIZE);
348 if batch_size == 0 || batch_size > 16384 {
349 return Err(Error::new(
350 Status::InvalidArg,
351 format!("batch size must be between 1 and 16384, got {}", batch_size),
352 ));
353 }
354
355 Ok(AsyncTask::new(NextBatchTask {
356 inner: self.inner.clone(),
357 header: self.header.clone(),
358 size: batch_size,
359 }))
360 }
361
362 #[napi(js_name = "nextSync")]
363 pub fn next_sync(&self, env: Env) -> napi::Result<Object<'static>> {
364 let mut guard = self
365 .inner
366 .lock()
367 .map_err(|_| Error::new(Status::GenericFailure, "reader lock poisoned"))?;
368 let reader = guard
369 .as_mut()
370 .ok_or_else(|| Error::new(Status::GenericFailure, "reader is closed"))?;
371
372 let rec = reader
373 .next_record()
374 .map_err(|e| Error::new(Status::GenericFailure, format!("read failed: {e}")))?;
375
376 let mut out: Object<'static> = Object::new(&env)?;
377
378 match rec.map(core::Variant::from_record) {
379 None => {
380 out.set_named_property("done", true)?;
381 out.set_named_property("value", ())?;
382 }
383 Some(variant) => {
384 out.set_named_property("done", false)?;
385 out.set_named_property(
386 "value",
387 Variant {
388 inner: Some(variant),
389 header: self.header.clone(),
390 },
391 )?;
392 }
393 }
394
395 Ok(out)
396 }
397
398 #[napi(js_name = "nextBatchSync")]
404 pub fn next_batch_sync(&self, size: Option<u32>) -> napi::Result<Vec<Variant>> {
405 let batch_size = size.map(|s| s as usize).unwrap_or(DEFAULT_BATCH_SIZE);
406 if batch_size == 0 || batch_size > 16384 {
407 return Err(Error::new(
408 Status::InvalidArg,
409 format!("batch size must be between 1 and 16384, got {}", batch_size),
410 ));
411 }
412
413 let mut reader_guard = self
414 .inner
415 .lock()
416 .map_err(|_| Error::new(Status::GenericFailure, "reader lock poisoned"))?;
417 let reader = reader_guard
418 .as_mut()
419 .ok_or_else(|| Error::new(Status::GenericFailure, "reader is closed"))?;
420
421 let mut variants = Vec::with_capacity(batch_size);
422
423 for _ in 0..batch_size {
424 let rec = reader
425 .next_record()
426 .map_err(|e| Error::new(Status::GenericFailure, format!("read failed: {e}")))?;
427
428 match rec {
429 None => break, Some(record) => {
431 variants.push(Variant {
432 inner: Some(core::Variant::from_record(record)),
433 header: self.header.clone(),
434 });
435 }
436 }
437 }
438
439 Ok(variants)
440 }
441
442 #[napi]
443 pub fn close(&self) {
444 if let Ok(mut guard) = self.inner.lock() {
445 let _ = guard.take();
446 }
447 }
448}
449
450#[napi]
451pub fn open_reader(path: String, opts: Option<ReaderOptions>) -> AsyncTask<OpenReaderTask> {
452 AsyncTask::new(OpenReaderTask { path, opts })
453}
454
455#[napi]
456pub struct Writer {
457 inner: Arc<Mutex<Option<core::Writer>>>,
458 header_ref: Reference<Header>,
466}
467
468#[napi]
469impl Writer {
470 #[napi(constructor)]
471 pub fn new(
472 env: Env,
473 path: String,
474 header: &Header,
475 opts: Option<WriterOptions>,
476 ) -> napi::Result<Self> {
477 let mut options = core::WriterOptions::default();
478
479 if let Some(opts) = opts {
480 if let Some(format) = opts.format {
481 options.format = match format.as_str() {
482 "vcf" => Some(core::OutputFormat::Vcf),
483 "bcf" => Some(core::OutputFormat::Bcf),
484 _ => {
485 return Err(Error::new(
486 Status::InvalidArg,
487 "WriterOptions.format must be 'vcf' or 'bcf'",
488 ))
489 }
490 };
491 }
492 if let Some(uncompressed) = opts.uncompressed {
493 options.uncompressed = uncompressed;
494 }
495 if let Some(threads) = opts.threads {
496 options.threads = Some(threads as usize);
497 }
498 }
499
500 let writer = core::open_writer(&path, header.inner.as_ref(), options).map_err(|e| {
501 Error::new(
502 Status::GenericFailure,
503 format!("failed to open writer: {e}"),
504 )
505 })?;
506
507 let header_ref = Header::into_reference(
508 Header {
509 inner: header.inner.clone(),
510 },
511 env,
512 )?;
513
514 Ok(Self {
515 inner: Arc::new(Mutex::new(Some(writer))),
516 header_ref,
517 })
518 }
519
520 #[napi(getter)]
521 pub fn header(&self, env: Env) -> napi::Result<Reference<Header>> {
522 self.header_ref.clone(env)
523 }
524
525 #[napi]
526 pub fn write(&self, variant: &mut Variant) -> napi::Result<()> {
527 let mut writer_guard = self
528 .inner
529 .lock()
530 .map_err(|_| Error::new(Status::GenericFailure, "writer lock poisoned"))?;
531 let writer = writer_guard
532 .as_mut()
533 .ok_or_else(|| Error::new(Status::GenericFailure, "writer is closed"))?;
534
535 let _header_keepalive = variant.header.clone();
539
540 let mut record = variant
541 .inner
542 .take()
543 .ok_or_else(|| Error::new(Status::GenericFailure, "variant was consumed"))?
544 .into_record();
545
546 writer
547 .write_record(&mut record)
548 .map_err(|e| Error::new(Status::GenericFailure, format!("write failed: {e}")))
549 }
550
551 #[napi]
552 pub fn close(&self) {
553 if let Ok(mut guard) = self.inner.lock() {
554 let _ = guard.take();
555 }
556 }
557}
558
559pub struct OpenReaderTask {
560 path: String,
561 #[allow(dead_code)]
562 opts: Option<ReaderOptions>,
563}
564
565impl Task for OpenReaderTask {
566 type Output = core::Reader;
567 type JsValue = Reader;
568
569 fn compute(&mut self) -> napi::Result<Self::Output> {
570 core::open_reader(&self.path).map_err(|e| {
571 Error::new(
572 Status::GenericFailure,
573 format!("failed to open {}: {e}", self.path),
574 )
575 })
576 }
577
578 fn resolve(&mut self, env: Env, output: Self::Output) -> napi::Result<Self::JsValue> {
579 let header = Arc::new(unsafe { core::Header::new(output.header_ptr()) });
580 let header_ref = Header::into_reference(
581 Header {
582 inner: header.clone(),
583 },
584 env,
585 )?;
586
587 Ok(Reader {
588 inner: Arc::new(Mutex::new(Some(output))),
589 header,
590 header_ref,
591 })
592 }
593}
594
595pub struct QueryTask {
596 inner: Arc<Mutex<Option<core::Reader>>>,
597 region_or_chrom: String,
598 start0: Option<u32>,
599 end0: Option<u32>,
600}
601
602impl Task for QueryTask {
603 type Output = ();
604 type JsValue = ();
605
606 fn compute(&mut self) -> napi::Result<Self::Output> {
607 let mut guard = self
608 .inner
609 .lock()
610 .map_err(|_| Error::new(Status::GenericFailure, "reader lock poisoned"))?;
611 let reader = guard
612 .as_mut()
613 .ok_or_else(|| Error::new(Status::GenericFailure, "reader is closed"))?;
614
615 if !reader.has_index() {
616 return Err(Error::new(
617 Status::GenericFailure,
618 "query() requires an indexed file",
619 ));
620 }
621
622 reader
623 .query(
624 &self.region_or_chrom,
625 self.start0.map(|v| v as u64),
626 self.end0.map(|v| v as u64),
627 )
628 .map_err(|e| Error::new(Status::GenericFailure, format!("query failed: {e}")))?;
629
630 Ok(())
631 }
632
633 fn resolve(&mut self, _env: Env, _output: Self::Output) -> napi::Result<Self::JsValue> {
634 Ok(())
635 }
636}
637
638pub struct NextTask {
639 inner: Arc<Mutex<Option<core::Reader>>>,
640 header: Arc<core::Header>,
641}
642
643impl Task for NextTask {
644 type Output = Option<core::Variant>;
645 type JsValue = Object<'static>;
646
647 fn compute(&mut self) -> napi::Result<Self::Output> {
648 let mut guard = self
649 .inner
650 .lock()
651 .map_err(|_| Error::new(Status::GenericFailure, "reader lock poisoned"))?;
652 let reader = guard
653 .as_mut()
654 .ok_or_else(|| Error::new(Status::GenericFailure, "reader is closed"))?;
655
656 let rec = reader
657 .next_record()
658 .map_err(|e| Error::new(Status::GenericFailure, format!("read failed: {e}")))?;
659
660 Ok(rec.map(core::Variant::from_record))
661 }
662
663 fn resolve(&mut self, env: Env, output: Self::Output) -> napi::Result<Self::JsValue> {
664 let mut out: Object<'static> = Object::new(&env)?;
665
666 match output {
667 None => {
668 out.set_named_property("done", true)?;
669 out.set_named_property("value", ())?;
670 }
671 Some(variant) => {
672 out.set_named_property("done", false)?;
673 out.set_named_property(
674 "value",
675 Variant {
676 inner: Some(variant),
677 header: self.header.clone(),
678 },
679 )?;
680 }
681 }
682
683 Ok(out)
684 }
685}
686
687pub struct NextBatchTask {
688 inner: Arc<Mutex<Option<core::Reader>>>,
689 header: Arc<core::Header>,
690 size: usize,
691}
692
693impl Task for NextBatchTask {
694 type Output = Vec<core::Variant>;
695 type JsValue = Vec<Variant>;
696
697 fn compute(&mut self) -> napi::Result<Self::Output> {
698 let mut guard = self
699 .inner
700 .lock()
701 .map_err(|_| Error::new(Status::GenericFailure, "reader lock poisoned"))?;
702 let reader = guard
703 .as_mut()
704 .ok_or_else(|| Error::new(Status::GenericFailure, "reader is closed"))?;
705
706 let mut variants = Vec::with_capacity(self.size);
707
708 for _ in 0..self.size {
709 let rec = reader
710 .next_record()
711 .map_err(|e| Error::new(Status::GenericFailure, format!("read failed: {e}")))?;
712
713 match rec {
714 None => break, Some(record) => {
716 variants.push(core::Variant::from_record(record));
717 }
718 }
719 }
720
721 Ok(variants)
722 }
723
724 fn resolve(&mut self, _env: Env, output: Self::Output) -> napi::Result<Self::JsValue> {
725 Ok(output
726 .into_iter()
727 .map(|v| Variant {
728 inner: Some(v),
729 header: self.header.clone(),
730 })
731 .collect())
732 }
733}
734
735#[napi]
736pub struct Variant {
737 pub(crate) inner: Option<core::Variant>,
738 header: Arc<core::Header>,
739}
740
741#[napi]
742impl Variant {
743 fn variant(&self) -> napi::Result<&core::Variant> {
744 self.inner
745 .as_ref()
746 .ok_or_else(|| Error::new(Status::GenericFailure, "variant was consumed"))
747 }
748
749 fn variant_mut(&mut self) -> napi::Result<&mut core::Variant> {
750 self.inner
751 .as_mut()
752 .ok_or_else(|| Error::new(Status::GenericFailure, "variant was consumed"))
753 }
754
755 #[napi(getter)]
756 pub fn chrom(&self) -> napi::Result<String> {
757 Ok(self.variant()?.chrom().to_string())
758 }
759
760 #[napi(getter)]
761 pub fn rid(&self) -> napi::Result<Option<u32>> {
762 Ok(self.variant()?.rid())
763 }
764
765 #[napi(getter)]
766 pub fn pos(&self) -> napi::Result<i64> {
767 Ok(self.variant()?.pos())
768 }
769
770 #[napi(getter)]
771 pub fn start(&self) -> napi::Result<i64> {
772 Ok(self.variant()?.start())
773 }
774
775 #[napi(getter, js_name = "stop")]
776 pub fn stop(&self) -> napi::Result<i64> {
777 Ok(self.variant()?.end())
778 }
779
780 #[napi(getter)]
781 pub fn id(&self) -> napi::Result<String> {
782 Ok(self.variant()?.id())
783 }
784
785 #[napi(setter)]
786 pub fn set_id(&mut self, id: String) -> napi::Result<()> {
787 self.variant_mut()?
788 .set_id(&id)
789 .map_err(|e| Error::new(Status::GenericFailure, format!("failed to set id: {e}")))
790 }
791
792 #[napi(getter, js_name = "ref")]
793 pub fn reference(&self) -> napi::Result<String> {
794 Ok(self.variant()?.reference())
795 }
796
797 #[napi(getter)]
798 pub fn alt(&self) -> napi::Result<Vec<String>> {
799 Ok(self.variant()?.alts())
800 }
801
802 #[napi(getter)]
803 pub fn qual(&self) -> napi::Result<Option<f64>> {
804 Ok(self.variant()?.qual().map(|v| v as f64))
805 }
806
807 #[napi(setter)]
808 pub fn set_qual(&mut self, qual: Option<f64>) -> napi::Result<()> {
809 self.variant_mut()?.set_qual(qual.map(|v| v as f32));
810 Ok(())
811 }
812
813 #[napi(getter)]
814 pub fn filter(&self) -> napi::Result<Vec<String>> {
815 Ok(self.variant()?.filters())
816 }
817
818 #[napi(setter)]
819 pub fn set_filter(&mut self, filter: Vec<String>) -> napi::Result<()> {
820 self.variant_mut()?
821 .set_filters(&filter)
822 .map_err(|e| Error::new(Status::GenericFailure, format!("failed to set filter: {e}")))
823 }
824
825 #[napi(js_name = "toString")]
826 pub fn to_string(&self) -> napi::Result<String> {
827 self.variant()?
828 .to_string(&self.header)
829 .ok_or_else(|| Error::new(Status::GenericFailure, "failed to format record"))
830 }
831
832 #[napi]
833 pub fn info(&self, env: Env, tag: String) -> napi::Result<sys::napi_value> {
834 let v = self.variant()?.info(&self.header, &tag);
835 infovalue_to_napi_value(&env, &v)
836 }
837
838 #[napi]
839 pub fn format(&self, env: Env, tag: String) -> napi::Result<sys::napi_value> {
840 let v = self.variant()?.format(&self.header, &tag);
841 formatvalue_to_napi_value(&env, &v)
842 }
843
844 #[napi]
845 pub fn sample(&self, env: Env, name: String) -> napi::Result<sys::napi_value> {
846 let Some(fields) = self.variant()?.sample(&self.header, &name) else {
847 return unsafe { ToNapiValue::to_napi_value(env.raw(), ()) };
848 };
849
850 let mut out: Object<'static> = Object::new(&env)?;
851 for (tag, value) in fields {
852 let js_value = formatvalue_to_napi_value(&env, &value)?;
853 out.set_named_property(tag.as_str(), unsafe {
854 Unknown::from_raw_unchecked(env.raw(), js_value)
855 })?;
856 }
857
858 Ok(out.raw())
859 }
860
861 #[napi]
862 pub fn samples(&self, env: Env, subset: Option<Vec<String>>) -> napi::Result<sys::napi_value> {
863 let subset_refs: Option<Vec<&str>> = subset
864 .as_ref()
865 .map(|v| v.iter().map(|s| s.as_str()).collect());
866 let all_samples = self
867 .variant()?
868 .samples(&self.header, subset_refs.as_deref());
869
870 let mut arr_items: Vec<sys::napi_value> = Vec::with_capacity(all_samples.len());
871
872 for fields in all_samples {
873 let mut out: Object<'static> = Object::new(&env)?;
874 for (tag, value) in fields {
875 let js_value = formatvalue_to_napi_value(&env, &value)?;
876 out.set_named_property(tag.as_str(), unsafe {
877 Unknown::from_raw_unchecked(env.raw(), js_value)
878 })?;
879 }
880 arr_items.push(out.raw());
881 }
882
883 let arr = Array::from_vec(&env, arr_items)?;
884 Ok(arr.raw())
885 }
886
887 #[napi]
888 pub fn genotypes(&self, subset: Option<Vec<String>>) -> napi::Result<Vec<Genotype>> {
889 let subset_refs: Option<Vec<&str>> = subset
890 .as_ref()
891 .map(|v| v.iter().map(|s| s.as_str()).collect());
892 let genotypes = self
893 .variant()?
894 .genotypes(&self.header, subset_refs.as_deref());
895
896 Ok(genotypes.into_iter().map(Genotype::from).collect())
897 }
898
899 #[napi(js_name = "set_genotypes")]
900 pub fn set_genotypes(&mut self, genotypes: Vec<Genotype>) -> napi::Result<()> {
901 let gts: Vec<core::Genotype> = genotypes.into_iter().map(core::Genotype::from).collect();
902 self.variant_mut()?.set_genotypes(>s).map_err(|e| {
903 Error::new(
904 Status::GenericFailure,
905 format!("failed to set genotypes: {e}"),
906 )
907 })
908 }
909
910 #[napi]
911 pub fn translate(&mut self, header: &Header) -> napi::Result<()> {
912 self.header = header.inner.clone();
913
914 self.variant_mut()?
915 .translate(&header.inner)
916 .map_err(|e| Error::new(Status::GenericFailure, format!("translate failed: {e}")))
917 }
918
919 #[napi(js_name = "set_info")]
920 pub fn set_info(&mut self, tag: String, value: Unknown) -> napi::Result<()> {
921 use napi::ValueType;
922 use rust_htslib::bcf::header::TagType;
923
924 let header = self.header.clone();
925
926 let Some((tag_type, _tag_length)) = header.info_type(tag.as_bytes()) else {
927 return Err(Error::new(
928 Status::InvalidArg,
929 format!("undefined INFO tag: {tag}"),
930 ));
931 };
932
933 match value.get_type()? {
934 ValueType::Null | ValueType::Undefined => {
935 self.variant_mut()?.clear_info(&header, &tag).map_err(|e| {
936 Error::new(
937 Status::GenericFailure,
938 format!("failed to clear info {tag}: {e}"),
939 )
940 })?;
941 return Ok(());
942 }
943 _ => {}
944 }
945
946 match tag_type {
947 TagType::Flag => {
948 if value.is_array()? {
949 return Err(Error::new(
950 Status::InvalidArg,
951 format!("INFO/{tag} is Flag; expected boolean"),
952 ));
953 }
954
955 if value.get_type()? != ValueType::Boolean {
956 return Err(Error::new(
957 Status::InvalidArg,
958 format!("INFO/{tag} is Flag; expected boolean"),
959 ));
960 }
961
962 let is_set: bool = unsafe { value.cast()? };
963 self.variant_mut()?
964 .set_info_flag(&header, &tag, is_set)
965 .map_err(|e| {
966 Error::new(
967 Status::GenericFailure,
968 format!("failed to set info {tag}: {e}"),
969 )
970 })?;
971 }
972 TagType::Integer => {
973 let values = unknown_to_numbers(&tag, value)?;
974 let mut out: Vec<i32> = Vec::with_capacity(values.len());
975 for n in values {
976 if !n.is_finite() {
977 return Err(Error::new(Status::InvalidArg, "number must be finite"));
978 }
979 if n.fract() != 0.0 {
980 return Err(Error::new(
981 Status::InvalidArg,
982 format!("INFO/{tag} is Integer; got non-integer value"),
983 ));
984 }
985 if n < (i32::MIN as f64) || n > (i32::MAX as f64) {
986 return Err(Error::new(
987 Status::InvalidArg,
988 format!("INFO/{tag} integer out of range"),
989 ));
990 }
991 out.push(n as i32);
992 }
993
994 self.variant_mut()?
995 .set_info_integer(&header, &tag, &out)
996 .map_err(|e| {
997 Error::new(
998 Status::GenericFailure,
999 format!("failed to set info {tag}: {e}"),
1000 )
1001 })?;
1002 }
1003 TagType::Float => {
1004 let values = unknown_to_numbers(&tag, value)?;
1005 let mut out: Vec<f32> = Vec::with_capacity(values.len());
1006 for n in values {
1007 if !n.is_finite() {
1008 return Err(Error::new(Status::InvalidArg, "number must be finite"));
1009 }
1010 out.push(n as f32);
1011 }
1012
1013 self.variant_mut()?
1014 .set_info_float(&header, &tag, &out)
1015 .map_err(|e| {
1016 Error::new(
1017 Status::GenericFailure,
1018 format!("failed to set info {tag}: {e}"),
1019 )
1020 })?;
1021 }
1022 TagType::String => {
1023 let values: Vec<String> = unknown_to_strings(&tag, value)?;
1024 self.variant_mut()?
1025 .set_info_string(&header, &tag, &values)
1026 .map_err(|e| {
1027 Error::new(
1028 Status::GenericFailure,
1029 format!("failed to set info {tag}: {e}"),
1030 )
1031 })?;
1032 }
1033 }
1034
1035 Ok(())
1036 }
1037
1038 #[napi(js_name = "set_format")]
1047 pub fn set_format(&mut self, tag: String, value: Unknown) -> napi::Result<()> {
1048 use napi::ValueType;
1049 use rust_htslib::bcf::header::TagType;
1050
1051 if tag == "GT" {
1053 return Err(Error::new(
1054 Status::InvalidArg,
1055 "GT cannot be set via set_format; use dedicated genotype methods",
1056 ));
1057 }
1058
1059 let header = self.header.clone();
1060
1061 let Some((tag_type, _tag_length)) = header.format_type(tag.as_bytes()) else {
1062 return Err(Error::new(
1063 Status::InvalidArg,
1064 format!("undefined FORMAT tag: {tag}"),
1065 ));
1066 };
1067
1068 match value.get_type()? {
1070 ValueType::Null | ValueType::Undefined => {
1071 let variant = self.variant_mut()?;
1072 variant.clear_format(&header, &tag).map_err(|e| {
1073 Error::new(
1074 Status::GenericFailure,
1075 format!("failed to clear format {tag}: {e}"),
1076 )
1077 })?;
1078 return Ok(());
1079 }
1080 _ => {}
1081 }
1082
1083 if !value.is_array()? {
1085 return Err(Error::new(
1086 Status::InvalidArg,
1087 "variant.set_format values must be an array",
1088 ));
1089 }
1090
1091 let arr: Array = unsafe { value.cast()? };
1092 let sample_count = self.header.sample_count() as u32;
1093
1094 if arr.len() != sample_count {
1095 return Err(Error::new(
1096 Status::InvalidArg,
1097 format!(
1098 "variant.set_format array length ({}) must match sample count ({})",
1099 arr.len(),
1100 sample_count
1101 ),
1102 ));
1103 }
1104
1105 let missing_int = htsvcf_core::format_int_missing();
1106 let missing_float = htsvcf_core::format_float_missing();
1107
1108 match tag_type {
1109 TagType::Integer => {
1110 let flattened = format::flatten_format_integers(&tag, &arr, missing_int)?;
1111 self.variant_mut()?
1112 .set_format_integer(&header, &tag, &flattened)
1113 .map_err(|e| {
1114 Error::new(
1115 Status::GenericFailure,
1116 format!("failed to set format {tag}: {e}"),
1117 )
1118 })?;
1119 }
1120 TagType::Float => {
1121 let flattened = format::flatten_format_floats(&tag, &arr, missing_float)?;
1122 self.variant_mut()?
1123 .set_format_float(&header, &tag, &flattened)
1124 .map_err(|e| {
1125 Error::new(
1126 Status::GenericFailure,
1127 format!("failed to set format {tag}: {e}"),
1128 )
1129 })?;
1130 }
1131 TagType::String => {
1132 let strings = format::flatten_format_strings(&tag, &arr)?;
1133 self.variant_mut()?
1134 .set_format_string(&header, &tag, &strings)
1135 .map_err(|e| {
1136 Error::new(
1137 Status::GenericFailure,
1138 format!("failed to set format {tag}: {e}"),
1139 )
1140 })?;
1141 }
1142 TagType::Flag => {
1143 return Err(Error::new(
1144 Status::InvalidArg,
1145 format!("FORMAT/{tag} is a Flag type which is not supported"),
1146 ));
1147 }
1148 }
1149
1150 Ok(())
1151 }
1152}
1153
1154fn unknown_to_numbers(tag: &str, value: Unknown) -> napi::Result<Vec<f64>> {
1155 if value.is_array()? {
1156 let arr: Array = unsafe { value.cast()? };
1157 let len = arr.len();
1158 let mut out = Vec::with_capacity(len as usize);
1159 for i in 0..len {
1160 let v: Unknown = arr.get_element(i)?;
1161 let n = unknown_to_number(tag, v)?;
1162 out.push(n);
1163 }
1164 return Ok(out);
1165 }
1166
1167 Ok(vec![unknown_to_number(tag, value)?])
1168}
1169
1170fn unknown_to_number(tag: &str, value: Unknown) -> napi::Result<f64> {
1171 use napi::ValueType;
1172
1173 if value.get_type()? != ValueType::Number {
1174 return Err(Error::new(
1175 Status::InvalidArg,
1176 format!("INFO/{tag} expected number"),
1177 ));
1178 }
1179
1180 let n: f64 = unsafe { value.cast()? };
1181 Ok(n)
1182}
1183
1184fn unknown_to_strings(tag: &str, value: Unknown) -> napi::Result<Vec<String>> {
1185 if value.is_array()? {
1186 let arr: Array = unsafe { value.cast()? };
1187 let len = arr.len();
1188 let mut out = Vec::with_capacity(len as usize);
1189 for i in 0..len {
1190 let v: Unknown = arr.get_element(i)?;
1191 let s = unknown_to_string(tag, v)?;
1192 out.push(s);
1193 }
1194 return Ok(out);
1195 }
1196
1197 Ok(vec![unknown_to_string(tag, value)?])
1198}
1199
1200fn unknown_to_string(tag: &str, value: Unknown) -> napi::Result<String> {
1201 use napi::ValueType;
1202
1203 if value.get_type()? != ValueType::String {
1204 return Err(Error::new(
1205 Status::InvalidArg,
1206 format!("INFO/{tag} expected string"),
1207 ));
1208 }
1209
1210 let s: String = unsafe { value.cast()? };
1211 Ok(s)
1212}
1213
1214#[napi]
1215pub struct Header {
1216 pub(crate) inner: Arc<core::Header>,
1217}
1218
1219#[napi]
1220impl Header {
1221 #[napi(js_name = "toString")]
1222 pub fn to_string(&self) -> napi::Result<String> {
1223 self.inner
1224 .to_string()
1225 .ok_or_else(|| Error::new(Status::GenericFailure, "failed to format header"))
1226 }
1227
1228 #[napi(js_name = "addInfo")]
1229 pub fn add_info(&self, id: String, number: String, ty: String, description: String) {
1230 let _ = self.inner.add_info(&id, &number, &ty, &description);
1231 }
1232
1233 #[napi(js_name = "addFormat")]
1234 pub fn add_format(&self, id: String, number: String, ty: String, description: String) {
1235 let _ = self.inner.add_format(&id, &number, &ty, &description);
1236 }
1237
1238 #[napi]
1239 pub fn get(&self, section: String, id: String) -> Option<HeaderField> {
1240 self.inner.get_field(§ion, &id).map(HeaderField::from)
1241 }
1242
1243 #[napi]
1244 pub fn records(&self) -> Vec<HeaderRecord> {
1245 self.inner
1246 .all_fields()
1247 .into_iter()
1248 .map(|(section, field)| HeaderRecord {
1249 section,
1250 id: field.id,
1251 r#type: field.r#type,
1252 number: field.number,
1253 description: field.description,
1254 })
1255 .collect()
1256 }
1257
1258 #[napi]
1259 pub fn samples(&self) -> Vec<String> {
1260 self.inner.sample_names().to_vec()
1261 }
1262}
1263
1264#[napi(object)]
1266pub struct HeaderField {
1267 pub id: String,
1268 #[napi(js_name = "type")]
1269 pub r#type: String,
1270 pub number: String,
1271 pub description: String,
1272}
1273
1274impl From<core::header::HeaderField> for HeaderField {
1275 fn from(f: core::header::HeaderField) -> Self {
1276 HeaderField {
1277 id: f.id,
1278 r#type: f.r#type,
1279 number: f.number,
1280 description: f.description,
1281 }
1282 }
1283}
1284
1285#[napi(object)]
1287pub struct HeaderRecord {
1288 pub section: String,
1289 pub id: String,
1290 #[napi(js_name = "type")]
1291 pub r#type: String,
1292 pub number: String,
1293 pub description: String,
1294}
1295
1296fn infovalue_to_napi_value(env: &Env, v: &core::InfoValue) -> napi::Result<sys::napi_value> {
1297 match v {
1298 core::InfoValue::Absent => unsafe { ToNapiValue::to_napi_value(env.raw(), ()) },
1299 core::InfoValue::Missing => unsafe { ToNapiValue::to_napi_value(env.raw(), Null) },
1300 core::InfoValue::Bool(b) => unsafe { ToNapiValue::to_napi_value(env.raw(), *b) },
1301 core::InfoValue::Int(i) => unsafe {
1302 ToNapiValue::to_napi_value(env.raw(), env.create_int32(*i)?)
1303 },
1304 core::InfoValue::Float(f) => unsafe {
1305 ToNapiValue::to_napi_value(env.raw(), env.create_double(*f as f64)?)
1306 },
1307 core::InfoValue::String(s) => unsafe {
1308 ToNapiValue::to_napi_value(env.raw(), env.create_string(s)?)
1309 },
1310 core::InfoValue::Array(values) => {
1311 let inner_values = values
1312 .iter()
1313 .map(|item| infovalue_to_napi_value(env, item))
1314 .collect::<napi::Result<Vec<sys::napi_value>>>()?;
1315 let arr = Array::from_vec(env, inner_values)?;
1316 Ok(arr.raw())
1317 }
1318 }
1319}
1320
1321fn formatvalue_to_napi_value(env: &Env, v: &FormatValue) -> napi::Result<sys::napi_value> {
1322 match v {
1323 FormatValue::Absent => unsafe { ToNapiValue::to_napi_value(env.raw(), ()) },
1324 FormatValue::Missing => unsafe { ToNapiValue::to_napi_value(env.raw(), Null) },
1325 FormatValue::Int(i) => unsafe {
1326 ToNapiValue::to_napi_value(env.raw(), env.create_int32(*i)?)
1327 },
1328 FormatValue::Float(f) => unsafe {
1329 ToNapiValue::to_napi_value(env.raw(), env.create_double(*f as f64)?)
1330 },
1331 FormatValue::String(s) => unsafe {
1332 ToNapiValue::to_napi_value(env.raw(), env.create_string(s)?)
1333 },
1334 FormatValue::Array(values) => {
1335 let inner_values = values
1336 .iter()
1337 .map(|item| formatvalue_to_napi_value(env, item))
1338 .collect::<napi::Result<Vec<sys::napi_value>>>()?;
1339 let arr = Array::from_vec(env, inner_values)?;
1340 Ok(arr.raw())
1341 }
1342 FormatValue::PerSample(values) => {
1343 let inner_values = values
1344 .iter()
1345 .map(|item| formatvalue_to_napi_value(env, item))
1346 .collect::<napi::Result<Vec<sys::napi_value>>>()?;
1347 let arr = Array::from_vec(env, inner_values)?;
1348 Ok(arr.raw())
1349 }
1350 FormatValue::Genotype(gt) => {
1351 let genotype = Genotype::from(gt.clone());
1352 unsafe { ToNapiValue::to_napi_value(env.raw(), genotype) }
1353 }
1354 }
1355}