11use anyhow:: { anyhow, bail, Result } ;
22use compiler:: modules:: lock:: { locked_spec, verify_pin} ;
3- use compiler:: modules:: rules:: HOLDERS ;
43use compiler:: modules:: { dir_of, join_relative, parse_integrity, walk_up_dirs} ;
54use compiler:: util:: sha256:: { hex_encode, sha256} ;
65use serde_json:: { json, Map , Value } ;
7- use std:: collections:: { HashMap , HashSet } ;
6+ use std:: collections:: HashMap ;
87use std:: path:: { Path , PathBuf } ;
98use std:: rc:: Rc ;
109
@@ -27,25 +26,21 @@ impl Home {
2726 }
2827}
2928
30- /* A package in the tree , the name the root grants it by and what it asks . */
29+ /* A package the root imports , the key it is granted by and what its section lists . */
3130pub struct Package {
3231 pub name : String ,
3332 label : String ,
34- via : Vec < String > ,
3533 pub section : Value ,
3634}
3735
3836impl Package {
39- /// The package and the packages it came through, as a report names it .
37+ /// The package as a report names it, its version beside the key it is granted by .
4038 pub fn who ( & self ) -> String {
41- match self . via . is_empty ( ) {
42- true => self . label . clone ( ) ,
43- false => format ! ( "{}, via {}" , self . label, self . via. join( " > " ) ) ,
44- }
39+ self . label . clone ( )
4540 }
4641}
4742
48- /* Stops when the root edge.json misses what a package in its tree asks for . */
43+ /* Stops when the root edge.json misses what a package it imports lists . */
4944pub fn check ( path : & Path , lock : & Lock ) -> Result < ( ) > {
5045 let Ok ( bytes) = std:: fs:: read ( path) else { return Ok ( ( ) ) } ;
5146 let manifest: Value = serde_json:: from_slice ( & bytes) . map_err ( |e| anyhow ! ( "parsing {}: {e}" , path. display( ) ) ) ?;
@@ -56,7 +51,7 @@ pub fn check(path: &Path, lock: &Lock) -> Result<()> {
5651 let imports = manifest. get ( "imports" ) . and_then ( Value :: as_object) . cloned ( ) . unwrap_or_default ( ) ;
5752 let mut unmet = Vec :: new ( ) ;
5853 for package in packages ( path, & imports, lock) ?. iter ( ) . filter ( |p| !p. section . is_null ( ) ) {
59- let missing = system:: unmet ( & grants, & package. name , & package. section ) ;
54+ let missing = system:: unmet ( & system :: held ( & json ! ( [ [ grants, package. name] ] ) ) , & package. section ) ;
6055 if !missing. is_empty ( ) {
6156 unmet. push ( ( package. who ( ) , missing. join ( ", " ) ) ) ;
6257 }
@@ -69,23 +64,30 @@ pub fn check(path: &Path, lock: &Lock) -> Result<()> {
6964 bail ! ( "edge.json does not grant what these packages ask for\n {}\n help: grant them under \" permissions\" in edge.json" , lines. join( "\n " ) )
7065}
7166
72- /* Every package the imports reach , each through its own edge.json and edge.lock . */
67+ /* Every package the root imports , each tree below it held to what its importers pass on . */
7368pub fn packages ( path : & Path , imports : & Map < String , Value > , lock : & Lock ) -> Result < Vec < Package > > {
7469 let project = path. parent ( ) . filter ( |p| !p. as_os_str ( ) . is_empty ( ) ) . unwrap_or ( Path :: new ( "." ) ) ;
7570 // The root grants, so it is never one of the packages it checks.
76- let mut walk = Walk { seen : HashSet :: from ( [ key ( "" , "" ) ] ) , found : Vec :: new ( ) } ;
77- walk. imports ( & Rc :: new ( Home :: Disk ( project. to_path_buf ( ) ) ) , "" , "" , & [ ] , imports, lock) ?;
71+ let mut walk = Walk { found : Vec :: new ( ) , path : vec ! [ key( "" , "" ) ] } ;
72+ walk. imports ( & Rc :: new ( Home :: Disk ( project. to_path_buf ( ) ) ) , "" , "" , None , imports, lock) ?;
7873 Ok ( walk. found )
7974}
8075
76+ /* A non-root importer, as a report names it, and the section it passes on from. */
77+ struct Importer < ' a > {
78+ label : & ' a str ,
79+ section : & ' a Value ,
80+ }
81+
8182struct Walk {
82- seen : HashSet < String > ,
8383 found : Vec < Package > ,
84+ // The packages from the root down to the one being read.
85+ path : Vec < String > ,
8486}
8587
8688impl Walk {
8789 /* Follows each import of the package at `dir`, as the lock beside its manifest resolved it. */
88- fn imports ( & mut self , home : & Rc < Home > , id : & str , dir : & str , via : & [ String ] , imports : & Map < String , Value > , lock : & Lock ) -> Result < ( ) > {
90+ fn imports ( & mut self , home : & Rc < Home > , id : & str , dir : & str , importer : Option < & Importer > , imports : & Map < String , Value > , lock : & Lock ) -> Result < ( ) > {
8991 for ( name, target) in imports {
9092 // A target the lock cannot answer yet is the command's own error to report.
9193 let Some ( spec) = target. as_str ( ) . and_then ( |t| locked_spec ( name, t, Some ( lock) ) . ok ( ) ) else { continue } ;
@@ -101,67 +103,73 @@ impl Walk {
101103 Some ( bytes) => bytes,
102104 None => fetch_cached ( target, pin) . map_err ( |e| anyhow ! ( e) ) ?,
103105 } ;
104- self . bundle ( target, & spec, & bytes, name, via ) ?;
106+ self . bundle ( target, & spec, & bytes, name, importer ) ?;
105107 }
106108 continue ;
107109 }
108110 let rel = join_relative ( dir, target) . trim_start_matches ( "./" ) . to_string ( ) ;
109111 if packed ( & rel) {
110112 let bytes = home. read ( & rel) . ok_or_else ( || anyhow ! ( "reading {rel}: not found" ) ) ?;
111- self . bundle ( & rel, & spec, & bytes, name, via ) ?;
113+ self . bundle ( & rel, & spec, & bytes, name, importer ) ?;
112114 continue ;
113115 }
114- // A module belongs to the nearest manifest above it, another package unless that is this one .
116+ // A module belongs to the nearest manifest above it, a new package unless it holds the importer .
115117 let nearest = walk_up_dirs ( & dir_of ( & rel) ) . find ( |d| d == dir || home. read ( & format ! ( "{d}edge.json" ) ) . is_some ( ) ) ;
116- if let Some ( found) = nearest. filter ( |d| d != dir ) {
118+ if let Some ( found) = nearest. filter ( |d| ! walk_up_dirs ( dir ) . any ( |around| around == * d ) ) {
117119 let place = format ! ( "{found}edge.json" ) ;
118- self . visit ( home, id, & found, & place, name, via ) ?;
120+ self . visit ( home, id, & found, & place, name, importer ) ?;
119121 }
120122 }
121123 Ok ( ( ) )
122124 }
123125
124126 /* A packed package, held to its pin, then read from inside itself like any other. */
125- fn bundle ( & mut self , address : & str , spec : & str , bytes : & [ u8 ] , name : & str , via : & [ String ] ) -> Result < ( ) > {
127+ fn bundle ( & mut self , address : & str , spec : & str , bytes : & [ u8 ] , name : & str , importer : Option < & Importer > ) -> Result < ( ) > {
126128 verify_pin ( spec, bytes) . map_err ( |e| anyhow ! ( e) ) ?;
127129 let files = crate :: pack:: into_files ( Bundle :: decode ( bytes) . map_err ( |e| anyhow ! ( "package '{address}' is not a packed .edge, {e}" ) ) ?) ;
128- self . visit ( & Rc :: new ( Home :: Packed ( files) ) , & hex_encode ( & sha256 ( bytes) ) , "" , address, name, via )
130+ self . visit ( & Rc :: new ( Home :: Packed ( files) ) , & hex_encode ( & sha256 ( bytes) ) , "" , address, name, importer )
129131 }
130132
131- /* Records the package whose manifest sits at `dir`, then follows what it imports in turn. */
132- fn visit ( & mut self , home : & Rc < Home > , id : & str , dir : & str , place : & str , hint : & str , via : & [ String ] ) -> Result < ( ) > {
133- if !self . seen . insert ( key ( id, dir) ) {
133+ /* Holds the package at `dir` to what its importer passes, then follows its own imports. */
134+ fn visit ( & mut self , home : & Rc < Home > , id : & str , dir : & str , place : & str , name : & str , importer : Option < & Importer > ) -> Result < ( ) > {
135+ let at = key ( id, dir) ;
136+ // A package importing one that imports it reaches that same package, as the engine reads it.
137+ if self . path . contains ( & at) {
134138 return Ok ( ( ) ) ;
135139 }
136140 let Some ( bytes) = home. read ( & format ! ( "{dir}edge.json" ) ) else { return Ok ( ( ) ) } ;
137141 let manifest: Value = serde_json:: from_slice ( & bytes) . map_err ( |e| anyhow ! ( "edge.json at '{place}': {e}" ) ) ?;
138- let named = manifest. get ( "name" ) . and_then ( Value :: as_str) ;
139- if let Some ( name) = named. filter ( |n| HOLDERS . contains ( n) ) {
140- bail ! ( "edge.json at '{place}': name '{name}' is reserved for permissions" ) ;
141- }
142- let name = named. unwrap_or ( hint) . to_string ( ) ;
143142 let section = manifest. get ( "permissions" ) . cloned ( ) . unwrap_or ( Value :: Null ) ;
144- if !section. is_null ( ) {
145- if let Some ( problem) = system:: check ( & section) {
146- bail ! ( "edge.json at '{place}': {problem}" ) ;
147- }
148- // The root grants by name, and against an empty grant every ask comes back unmet.
149- if named. is_none ( ) && !system:: unmet ( & json ! ( { } ) , & name, & section) . is_empty ( ) {
150- bail ! ( "edge.json at '{place}': a package that asks for permissions needs a name" ) ;
151- }
143+ if !section. is_null ( )
144+ && let Some ( problem) = system:: check ( & section)
145+ {
146+ bail ! ( "edge.json at '{place}': {problem}" ) ;
152147 }
153148 let label = match manifest. get ( "version" ) . and_then ( Value :: as_str) {
154149 Some ( version) => format ! ( "{name} {version}" ) ,
155- None => name. clone ( ) ,
150+ None => name. to_string ( ) ,
156151 } ;
152+ match importer {
153+ None => self . found . push ( Package { name : name. to_string ( ) , label : label. clone ( ) , section : section. clone ( ) } ) ,
154+ Some ( importer) if !section. is_null ( ) => {
155+ // What the importer writes for it, since the root check covers what the importer holds.
156+ let missing = system:: unmet ( & system:: held ( & json ! ( [ [ importer. section, name] ] ) ) , & section) ;
157+ if !missing. is_empty ( ) {
158+ bail ! ( "edge.json at '{place}' asks for {}, which {} does not pass it" , missing. join( ", " ) , importer. label) ;
159+ }
160+ }
161+ Some ( _) => { }
162+ }
157163 let lock = match home. read ( & format ! ( "{dir}{}" , lock:: FILE ) ) {
158164 Some ( bytes) => Lock :: parse ( & bytes) . map_err ( |e| anyhow ! ( "edge.json at '{place}': {e}" ) ) ?,
159165 None => Lock :: default ( ) ,
160166 } ;
161167 let imports = manifest. get ( "imports" ) . and_then ( Value :: as_object) . cloned ( ) . unwrap_or_default ( ) ;
162- let chain: Vec < String > = via. iter ( ) . cloned ( ) . chain ( [ label. clone ( ) ] ) . collect ( ) ;
163- self . found . push ( Package { name, label, via : via. to_vec ( ) , section } ) ;
164- self . imports ( home, id, dir, & chain, & imports, & lock)
168+ let given = if section. is_null ( ) { json ! ( { } ) } else { section } ;
169+ self . path . push ( at) ;
170+ let followed = self . imports ( home, id, dir, Some ( & Importer { label : & label, section : & given } ) , & imports, & lock) ;
171+ self . path . pop ( ) ;
172+ followed
165173 }
166174}
167175
0 commit comments