@@ -17,6 +17,7 @@ use std::collections::BTreeMap;
1717use std:: fmt;
1818use std:: str:: FromStr ;
1919
20+ use crate :: api:: internal:: shared:: DatasetKindParseError ;
2021use crate :: {
2122 api:: external:: { ByteCount , Generation } ,
2223 ledger:: Ledgerable ,
@@ -172,6 +173,69 @@ impl DatasetName {
172173 }
173174}
174175
176+ #[ derive( Debug , thiserror:: Error ) ]
177+ pub enum DatasetNameParseError {
178+ #[ error( "missing '/' separator in dataset name {0}" ) ]
179+ MissingSlash ( String ) ,
180+ #[ error( "could not parse zpool name {zpool}: {err}" ) ]
181+ ParseZpoolName { zpool : String , err : String } ,
182+ #[ error( "could not parse dataset kind {kind}" ) ]
183+ ParseDatasetKind {
184+ kind : String ,
185+ #[ source]
186+ err : DatasetKindParseError ,
187+ } ,
188+ #[ error( "expected `crypt/` for kind {kind:?} in dataset name {name}" ) ]
189+ MissingCryptInName { kind : DatasetKind , name : String } ,
190+ #[ error( "unexpected `crypt/` for kind {kind:?} in dataset name {name}" ) ]
191+ UnexpectedCryptInName { kind : DatasetKind , name : String } ,
192+ }
193+
194+ impl FromStr for DatasetName {
195+ type Err = DatasetNameParseError ;
196+
197+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
198+ let ( pool_name, remainder) = s. split_once ( '/' ) . ok_or_else ( || {
199+ DatasetNameParseError :: MissingSlash ( s. to_string ( ) )
200+ } ) ?;
201+
202+ let pool_name = ZpoolName :: from_str ( pool_name) . map_err ( |err| {
203+ DatasetNameParseError :: ParseZpoolName {
204+ zpool : pool_name. to_string ( ) ,
205+ err,
206+ }
207+ } ) ?;
208+
209+ let ( kind_str, name_has_crypt) =
210+ if let Some ( remainder) = remainder. strip_prefix ( "crypt/" ) {
211+ ( remainder, true )
212+ } else {
213+ ( remainder, false )
214+ } ;
215+
216+ let kind = DatasetKind :: from_str ( kind_str) . map_err ( |err| {
217+ DatasetNameParseError :: ParseDatasetKind {
218+ kind : kind_str. to_string ( ) ,
219+ err,
220+ }
221+ } ) ?;
222+
223+ match ( kind. dataset_should_be_encrypted ( ) , name_has_crypt) {
224+ ( true , true ) | ( false , false ) => Ok ( Self { pool_name, kind } ) ,
225+ ( true , false ) => Err ( DatasetNameParseError :: MissingCryptInName {
226+ kind,
227+ name : s. to_string ( ) ,
228+ } ) ,
229+ ( false , true ) => {
230+ Err ( DatasetNameParseError :: UnexpectedCryptInName {
231+ kind,
232+ name : s. to_string ( ) ,
233+ } )
234+ }
235+ }
236+ }
237+ }
238+
175239#[ derive(
176240 Copy ,
177241 Clone ,
@@ -583,3 +647,24 @@ impl TryFrom<i64> for M2Slot {
583647 }
584648 }
585649}
650+
651+ #[ cfg( test) ]
652+ mod tests {
653+ use super :: * ;
654+ use test_strategy:: proptest;
655+
656+ #[ proptest]
657+ fn parse_dataset_name ( pool_id : [ u8 ; 16 ] , kind : DatasetKind ) {
658+ let pool_id = ZpoolUuid :: from_bytes ( pool_id) ;
659+ for pool in
660+ [ ZpoolName :: new_internal ( pool_id) , ZpoolName :: new_external ( pool_id) ]
661+ {
662+ let dataset_name = DatasetName :: new ( pool, kind. clone ( ) ) ;
663+ let s = dataset_name. full_name ( ) ;
664+ match DatasetName :: from_str ( & s) {
665+ Ok ( d) => assert_eq ! ( d, dataset_name) ,
666+ Err ( err) => panic ! ( "failed to parse dataset name {s}: {err}" ) ,
667+ }
668+ }
669+ }
670+ }
0 commit comments