@@ -25,7 +25,9 @@ use crate::manifest::{load_manifest, resolve_path, DataSource, Manifest, Templat
2525use crate :: migrations;
2626use crate :: output:: { step, Step } ;
2727use crate :: services:: IpfsClient ;
28- use crate :: validation:: { format_schema_errors, validate_schema} ;
28+ use crate :: validation:: {
29+ format_manifest_errors, format_schema_errors, validate_manifest_files, validate_schema,
30+ } ;
2931use crate :: watch:: watch_and_run;
3032
3133/// Default IPFS URL.
@@ -107,6 +109,20 @@ async fn generate_types(opt: &CodegenOpt) -> Result<()> {
107109 // Load the subgraph manifest
108110 let manifest = load_manifest ( & opt. manifest ) ?;
109111
112+ // Validate manifest file references (schema, mappings, ABIs exist and are valid)
113+ let source_dir = crate :: manifest:: manifest_dir ( & opt. manifest ) . to_path_buf ( ) ;
114+ let manifest_errors = validate_manifest_files ( & manifest, & source_dir) ;
115+ if !manifest_errors. is_empty ( ) {
116+ eprintln ! (
117+ "Manifest validation errors:\n {}" ,
118+ format_manifest_errors( & manifest_errors)
119+ ) ;
120+ return Err ( anyhow ! (
121+ "Manifest validation failed with {} error(s)" ,
122+ manifest_errors. len( )
123+ ) ) ;
124+ }
125+
110126 // Create output directory
111127 fs:: create_dir_all ( & opt. output_dir )
112128 . with_context ( || format ! ( "Failed to create output directory: {:?}" , opt. output_dir) ) ?;
@@ -503,6 +519,7 @@ schema:
503519dataSources:
504520 - kind: ethereum/contract
505521 name: ExampleSubgraph
522+ network: mainnet
506523 source:
507524 abi: ExampleContract
508525 mapping:
@@ -612,6 +629,7 @@ schema:
612629dataSources:
613630 - kind: ethereum/contract
614631 name: TestDataSource
632+ network: mainnet
615633 source:
616634 abi: TestContract
617635 mapping:
@@ -729,6 +747,7 @@ schema:
729747dataSources:
730748 - kind: ethereum/contract
731749 name: ExampleSubgraph
750+ network: mainnet
732751 source:
733752 abi: ExampleContract
734753 mapping:
@@ -822,4 +841,121 @@ dataSources:
822841 "Contract should have static bind method"
823842 ) ;
824843 }
844+
845+ /// Test that codegen fails when referenced ABI file does not exist.
846+ ///
847+ /// Before Step 2, codegen did not validate the manifest. Now it calls
848+ /// `validate_manifest()` which checks file existence, so a missing ABI
849+ /// file is caught before codegen even starts generating types.
850+ #[ tokio:: test]
851+ async fn test_codegen_fails_on_missing_abi_file ( ) {
852+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
853+ let project_dir = temp_dir. path ( ) ;
854+ let output_dir = project_dir. join ( "generated" ) ;
855+
856+ // Manifest references an ABI file that doesn't exist
857+ let manifest_content = r#"
858+ specVersion: 0.0.4
859+ schema:
860+ file: ./schema.graphql
861+ dataSources:
862+ - kind: ethereum/contract
863+ name: Token
864+ network: mainnet
865+ source:
866+ abi: ERC20
867+ mapping:
868+ kind: ethereum/events
869+ apiVersion: 0.0.5
870+ language: wasm/assemblyscript
871+ file: ./mapping.ts
872+ entities:
873+ - MyEntity
874+ abis:
875+ - name: ERC20
876+ file: ./abis/ERC20.json
877+ "# ;
878+ fs:: write ( project_dir. join ( "subgraph.yaml" ) , manifest_content) . unwrap ( ) ;
879+
880+ // Create schema and mapping, but NOT the ABI file
881+ fs:: write (
882+ project_dir. join ( "schema.graphql" ) ,
883+ "type MyEntity @entity { id: ID! }" ,
884+ )
885+ . unwrap ( ) ;
886+ fs:: write ( project_dir. join ( "mapping.ts" ) , "" ) . unwrap ( ) ;
887+
888+ let opt = CodegenOpt {
889+ manifest : project_dir. join ( "subgraph.yaml" ) ,
890+ output_dir,
891+ skip_migrations : true ,
892+ watch : false ,
893+ ipfs : "https://api.thegraph.com/ipfs/api/v0" . to_string ( ) ,
894+ } ;
895+
896+ let result = generate_types ( & opt) . await ;
897+ assert ! (
898+ result. is_err( ) ,
899+ "Codegen should fail when ABI file is missing"
900+ ) ;
901+ let err = format ! ( "{:#}" , result. unwrap_err( ) ) ;
902+ assert ! (
903+ err. contains( "validation failed" ) ,
904+ "Error should mention validation failure, got: {}" ,
905+ err
906+ ) ;
907+ }
908+
909+ /// Test that codegen fails when referenced schema file does not exist.
910+ #[ tokio:: test]
911+ async fn test_codegen_fails_on_missing_schema_file ( ) {
912+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
913+ let project_dir = temp_dir. path ( ) ;
914+ let output_dir = project_dir. join ( "generated" ) ;
915+
916+ // Manifest references a schema file that doesn't exist
917+ let manifest_content = r#"
918+ specVersion: 0.0.4
919+ schema:
920+ file: ./schema.graphql
921+ dataSources:
922+ - kind: ethereum/contract
923+ name: Token
924+ network: mainnet
925+ source:
926+ abi: ERC20
927+ mapping:
928+ kind: ethereum/events
929+ apiVersion: 0.0.5
930+ language: wasm/assemblyscript
931+ file: ./mapping.ts
932+ entities:
933+ - MyEntity
934+ abis: []
935+ "# ;
936+ fs:: write ( project_dir. join ( "subgraph.yaml" ) , manifest_content) . unwrap ( ) ;
937+
938+ // Create mapping, but NOT the schema file
939+ fs:: write ( project_dir. join ( "mapping.ts" ) , "" ) . unwrap ( ) ;
940+
941+ let opt = CodegenOpt {
942+ manifest : project_dir. join ( "subgraph.yaml" ) ,
943+ output_dir,
944+ skip_migrations : true ,
945+ watch : false ,
946+ ipfs : "https://api.thegraph.com/ipfs/api/v0" . to_string ( ) ,
947+ } ;
948+
949+ let result = generate_types ( & opt) . await ;
950+ assert ! (
951+ result. is_err( ) ,
952+ "Codegen should fail when schema file is missing"
953+ ) ;
954+ let err = format ! ( "{:#}" , result. unwrap_err( ) ) ;
955+ assert ! (
956+ err. contains( "validation failed" ) ,
957+ "Error should mention validation failure, got: {}" ,
958+ err
959+ ) ;
960+ }
825961}
0 commit comments