Skip to content

Commit 2cbe1b7

Browse files
committed
gnd: Wire manifest validation into codegen
Add file-level manifest validation to codegen so that missing schema files, mapping files, and ABI files are caught early during `gnd codegen` rather than only during `gnd build`. Introduce `validate_manifest_files()` which checks file existence and ABI validity without enforcing deployment-level concerns (network names, spec versions) that are irrelevant during code generation.
1 parent 44ca8f6 commit 2cbe1b7

2 files changed

Lines changed: 157 additions & 1 deletion

File tree

gnd/src/commands/codegen.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ use crate::manifest::{load_manifest, resolve_path, DataSource, Manifest, Templat
2525
use crate::migrations;
2626
use crate::output::{step, Step};
2727
use 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+
};
2931
use 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:
503519
dataSources:
504520
- kind: ethereum/contract
505521
name: ExampleSubgraph
522+
network: mainnet
506523
source:
507524
abi: ExampleContract
508525
mapping:
@@ -612,6 +629,7 @@ schema:
612629
dataSources:
613630
- kind: ethereum/contract
614631
name: TestDataSource
632+
network: mainnet
615633
source:
616634
abi: TestContract
617635
mapping:
@@ -729,6 +747,7 @@ schema:
729747
dataSources:
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
}

gnd/src/validation/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,26 @@ impl From<SubgraphManifestValidationError> for ManifestValidationError {
8686
}
8787
}
8888

89+
/// Validate manifest file references.
90+
///
91+
/// Checks that all referenced files exist and ABI files are valid JSON.
92+
/// This is the minimal validation needed before codegen — it doesn't check
93+
/// deployment-level concerns like network names or spec versions.
94+
pub(crate) fn validate_manifest_files(
95+
manifest: &Manifest,
96+
manifest_dir: &Path,
97+
) -> Vec<ManifestValidationError> {
98+
let mut errors = Vec::new();
99+
100+
// Validate file existence
101+
errors.extend(validate_file_existence(manifest, manifest_dir));
102+
103+
// Validate ABIs are valid JSON
104+
errors.extend(validate_abis(manifest, manifest_dir));
105+
106+
errors
107+
}
108+
89109
/// Validate a subgraph manifest.
90110
///
91111
/// This performs several validation checks:

0 commit comments

Comments
 (0)