@@ -163,9 +163,18 @@ pub struct PackageConfig {
163163 pub description : EcoString ,
164164 #[ serde( default , alias = "docs" ) ]
165165 pub documentation : Docs ,
166- #[ serde( default , serialize_with = "ordered_map" ) ]
166+ #[ serde(
167+ default ,
168+ serialize_with = "ordered_map" ,
169+ deserialize_with = "dependencies_map::deserialize"
170+ ) ]
167171 pub dependencies : Dependencies ,
168- #[ serde( default , alias = "dev-dependencies" , serialize_with = "ordered_map" ) ]
172+ #[ serde(
173+ default ,
174+ alias = "dev-dependencies" ,
175+ serialize_with = "ordered_map" ,
176+ deserialize_with = "dependencies_map::deserialize"
177+ ) ]
169178 pub dev_dependencies : Dependencies ,
170179 #[ serde( default ) ]
171180 pub repository : Option < Repository > ,
@@ -362,6 +371,34 @@ aide_generator = { git = "git@github.com:crowdhailer/aide.git", ref = "f559c5bc"
362371 insta:: assert_snapshot!( insta:: internals:: AutoName , error. pretty_string( ) ) ;
363372}
364373
374+ #[ test]
375+ fn invalid_dependency_name ( ) {
376+ let toml = r#"
377+ name = "wibble"
378+ version = "1.0.0"
379+
380+ [dependencies]
381+ Hello = "> 1.0.0"
382+ "# ;
383+ let error = deserialise_config ( "gleam.toml" , toml. into ( ) )
384+ . expect_err ( "should fail to deserialise because invalid name" ) ;
385+ insta:: assert_snapshot!( insta:: internals:: AutoName , error. pretty_string( ) ) ;
386+ }
387+
388+ #[ test]
389+ fn invalid_dev_dependency_name ( ) {
390+ let toml = r#"
391+ name = "wibble"
392+ version = "1.0.0"
393+
394+ [dev_dependencies]
395+ "yes." = "> 1.0.0"
396+ "# ;
397+ let error = deserialise_config ( "gleam.toml" , toml. into ( ) )
398+ . expect_err ( "should fail to deserialise because invalid name" ) ;
399+ insta:: assert_snapshot!( insta:: internals:: AutoName , error. pretty_string( ) ) ;
400+ }
401+
365402#[ test]
366403fn locked_no_manifest ( ) {
367404 let mut config = PackageConfig :: default ( ) ;
@@ -1065,11 +1102,8 @@ mod uri_serde_default_https {
10651102
10661103mod package_name {
10671104 use ecow:: EcoString ;
1068- use regex:: Regex ;
10691105 use serde:: Deserializer ;
1070- use std:: { fmt, sync:: OnceLock } ;
1071-
1072- static PACKAGE_NAME_PATTERN : OnceLock < Regex > = OnceLock :: new ( ) ;
1106+ use std:: fmt;
10731107
10741108 pub fn deserialize < ' de , D > ( deserializer : D ) -> Result < EcoString , D :: Error >
10751109 where
@@ -1091,10 +1125,7 @@ mod package_name {
10911125 where
10921126 E : serde:: de:: Error ,
10931127 {
1094- if PACKAGE_NAME_PATTERN
1095- . get_or_init ( || Regex :: new ( "^[a-z][a-z0-9_]*$" ) . expect ( "Package name regex" ) )
1096- . is_match ( value)
1097- {
1128+ if super :: is_valid_package_name ( value) {
10981129 Ok ( value. into ( ) )
10991130 } else {
11001131 let error =
@@ -1105,6 +1136,53 @@ mod package_name {
11051136 }
11061137}
11071138
1139+ mod dependencies_map {
1140+ use ecow:: EcoString ;
1141+ use serde:: { Deserialize , Deserializer , de} ;
1142+ use std:: collections:: HashMap ;
1143+
1144+ pub fn deserialize < ' de , D , V > ( deserializer : D ) -> Result < HashMap < EcoString , V > , D :: Error >
1145+ where
1146+ D : Deserializer < ' de > ,
1147+ V : Deserialize < ' de > ,
1148+ {
1149+ struct DependenciesVisitor < V > ( std:: marker:: PhantomData < V > ) ;
1150+
1151+ impl < ' de , V : Deserialize < ' de > > de:: Visitor < ' de > for DependenciesVisitor < V > {
1152+ type Value = HashMap < EcoString , V > ;
1153+
1154+ fn expecting ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1155+ write ! ( f, "a map with valid package name keys" )
1156+ }
1157+
1158+ fn visit_map < A : de:: MapAccess < ' de > > ( self , mut map : A ) -> Result < Self :: Value , A :: Error > {
1159+ let mut dependencies = HashMap :: new ( ) ;
1160+ while let Some ( key) = map. next_key :: < EcoString > ( ) ? {
1161+ if !super :: is_valid_package_name ( & key) {
1162+ return Err ( de:: Error :: invalid_value (
1163+ de:: Unexpected :: Str ( & key) ,
1164+ & "a package name containing only lowercase letter, numbers, and underscores" ,
1165+ ) ) ;
1166+ }
1167+ let _: Option < _ > = dependencies. insert ( key, map. next_value ( ) ?) ;
1168+ }
1169+ Ok ( dependencies)
1170+ }
1171+ }
1172+
1173+ deserializer. deserialize_map ( DependenciesVisitor ( std:: marker:: PhantomData ) )
1174+ }
1175+ }
1176+
1177+ fn is_valid_package_name ( name : & str ) -> bool {
1178+ use regex:: Regex ;
1179+ use std:: sync:: OnceLock ;
1180+ static PACKAGE_NAME_PATTERN : OnceLock < Regex > = OnceLock :: new ( ) ;
1181+ PACKAGE_NAME_PATTERN
1182+ . get_or_init ( || Regex :: new ( "^[a-z][a-z0-9_]*$" ) . expect ( "Package name regex" ) )
1183+ . is_match ( name)
1184+ }
1185+
11081186#[ test]
11091187fn name_with_dash ( ) {
11101188 let input = r#"
0 commit comments