123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163(*****************************************************************************)(* *)(* SPDX-License-Identifier: MIT *)(* Copyright (c) 2023 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(*****************************************************************************)typeerror+=|Installer_config_yaml_errorofstring|Installer_config_invalid|Installer_config_invalid_instructionofintlet()=register_error_kind`Permanent~id:"installer_config.yaml_error"~title:"The YAML file is illformed"~description:"The YAML file is illformed"~pp:(funppf->Format.fprintfppf"The given file is not a valid YAML: %s")Data_encoding.(obj1(req"yaml_msg"Data_encoding.string))(functionInstaller_config_yaml_errormsg->Somemsg|_->None)(funmsg->Installer_config_yaml_errormsg);register_error_kind`Permanent~id:"installer_config.invalid_config"~title:"The installer config is illformed"~description:"The installer config is illformed"~pp:(funppf()->Format.fprintfppf"The installer config has not the right format.\n")Data_encoding.unit(functionInstaller_config_invalid->Some()|_->None)(fun()->Installer_config_invalid);register_error_kind`Permanent~id:"installer_config.invalid_instruction"~title:"The installer config is illformed"~description:"The installer config is illformed with an invalid instruction"~pp:(funppf->Format.fprintfppf"The %dth instruction is invalid.\n")Data_encoding.(obj1(req"encoding_error"Data_encoding.int31))(functionInstaller_config_invalid_instructioni->Somei|_->None)(funi->Installer_config_invalid_instructioni)typeinstr=Setof{value:string;to_:string}typet=instrlistletinstr_encoding:instrData_encoding.t=letopenData_encodinginunion[case~title:"set"(Tag0)(obj1(req"set"(obj2(req"value"(optionstring))(req"to"string))))(fun(Set{value;to_})->let(`Hexvalue)=Hex.of_stringvalueinSome(Somevalue,to_))(fun(value,to_)->letvalue=Option.value~default:""valueinletvalue=Hex.to_string(`Hexvalue)|>Option.value~default:""inSet{value;to_});]letencoding=letopenData_encodinginobj1(req"instructions"(listinstr_encoding))letpp_instrfmt(Set{value;to_})=letopenFormatinfprintffmt"Set %S to %S"Hex.(of_stringvalue|>show)to_letppfmtconfig=letopenFormatinfprintffmt"Instructions:@.%a"(pp_print_list~pp_sep:pp_print_newlinepp_instr)configletmap_yaml_err=function|Okv->Okv|Error(`Msgerr)->Result_syntax.tzfail(Installer_config_yaml_errorerr)letyaml_parse_instriyaml=letopenResult_syntaxin(* By default, the library [Yaml] doesn't enforce implicite quote, we
need to it manually. *)letrecenforce_scalars_quoted=letopenYamlinfunction|`Scalarscalar->`Scalar{scalarwithquoted_implicit=true}|`Aliasa->`Aliasa|`Aseq->`A{seqwiths_members=List.mapenforce_scalars_quotedseq.s_members}|`Omapping->`O{mappingwithm_members=List.map(fun(y1,y2)->(enforce_scalars_quotedy1,enforce_scalars_quotedy2))mapping.m_members;}inmatchYaml.to_json(enforce_scalars_quotedyaml)with|Error_->tzfail(Installer_config_invalid_instructioni)|Okinstr->(tryOk(Data_encoding.Json.destructinstr_encodinginstr)with_->tzfail(Installer_config_invalid_instructioni))(* Note that the parsing is done manually on the Yaml representation rather than
the JSON obtained with {Yaml.to_json}, as it tends to stack overflow on big
configs (see https://github.com/avsm/ocaml-yaml/issues/70). *)letyaml_parse_instrs=letopenResult_syntaxinfunction|`OYaml.{m_members=[(`Scalar{value="instructions";_},`A{s_members=instrs;_})];_;}->let+instrs=List.rev_mapi_eyaml_parse_instrinstrsinList.revinstrs|_->tzfailInstaller_config_invalidletparse_yamlyaml=letopenResult_syntaxinlet*yaml=Yaml.yaml_of_stringyaml|>map_yaml_errinyaml_parse_instrsyamlletgenerate_yaml_instriinstr=matchData_encoding.Json.constructinstr_encodinginstr|>Yaml.of_jsonwith|Okyaml->Okyaml|Error_|(exception_)->(* Note that this error shouldn't happen in practice *)Result_syntax.tzfail(Installer_config_invalid_instructioni)letgenerate_yamlinstrs=letopenResult_syntaxinlet*instrs=List.rev_mapi_egenerate_yaml_instrinstrsin(* Similarly to {yaml_parse_instrs}, `Yaml.of_json` can stack overflow on big
objects. As such, we first generate an empty `instructions` object then
patch it the instructions generated individually. *)letempty_instructions=`O[("instructions",`A[])]inmatchYaml.of_jsonempty_instructionswith|Ok(`O({m_members=[(name,`Asequence)];_}asmapping))->Ok(`O{mappingwithm_members=[(name,`A{sequencewiths_members=instrs})];})|Error(`Msgerr)->tzfail(Installer_config_yaml_errorerr)|_->tzfailInstaller_config_invalidletemit_yamlinstrs=letopenResult_syntaxinlet*yaml=generate_yamlinstrsinYaml.yaml_to_stringyaml|>map_yaml_err