diff --git a/CHANGELOG.md b/CHANGELOG.md index 4234217b..b6c0d80c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Improved error messages when ABI generation fails due to missing `JsonSchema` trait implementation on custom types used in contract functions + ## [0.19.0](https://github.com/near/cargo-near/compare/cargo-near-v0.18.0...cargo-near-v0.19.0) - 2026-01-12 ### Added diff --git a/cargo-near-build/src/cargo_native/compile.rs b/cargo-near-build/src/cargo_native/compile.rs index 1a2e891c..1673a590 100644 --- a/cargo-near-build/src/cargo_native/compile.rs +++ b/cargo-near-build/src/cargo_native/compile.rs @@ -148,6 +148,7 @@ where // stdout and stderr have to be processed concurrently to not block the process from progressing let thread_stdout = thread::spawn(move || -> eyre::Result<_, std::io::Error> { let mut artifacts = vec![]; + let mut has_json_schema_error = false; let stdout_reader = std::io::BufReader::new(child_stdout); for message in Message::parse_stream(stdout_reader) { match message? { @@ -156,6 +157,13 @@ where } Message::CompilerMessage(message) => { if let Some(msg) = message.message.rendered { + // Check if this is a JsonSchema-related error. + // We use simple string matching as the error format is stable across rustc versions + // and this provides a good user experience without complex parsing. + // Example error: "the trait bound `Attestation: JsonSchema` is not satisfied" + if msg.contains("the trait bound") && msg.contains("JsonSchema") && msg.contains("is not satisfied") { + has_json_schema_error = true; + } for line in msg.lines() { eprintln!(" │ {line}"); } @@ -165,6 +173,29 @@ where }; } + if has_json_schema_error { + eprintln!(" │"); + eprintln!(" │ ╭─────────────────────────────────────────────────────────────────╮"); + eprintln!(" │ │ HINT: The error above indicates that a type used in your │"); + eprintln!(" │ │ contract's public interface does not implement the JsonSchema │"); + eprintln!(" │ │ trait, which is required for ABI generation. │"); + eprintln!(" │ │ │"); + eprintln!(" │ │ To fix this, add the JsonSchema derive to your struct/enum: │"); + eprintln!(" │ │ │"); + eprintln!(" │ │ Option 1 (Recommended - works with near-sdk 5.0+): │"); + eprintln!(" │ │ #[near(serializers = [json])] │"); + eprintln!(" │ │ pub struct YourType {{ ... }} │"); + eprintln!(" │ │ │"); + eprintln!(" │ │ Option 2 (Manual - for more control): │"); + eprintln!(" │ │ use schemars::JsonSchema; │"); + eprintln!(" │ │ #[derive(Serialize, Deserialize, JsonSchema)] │"); + eprintln!(" │ │ pub struct YourType {{ ... }} │"); + eprintln!(" │ │ │"); + eprintln!(" │ │ For more information, see: │"); + eprintln!(" │ │ https://github.com/near/near-sdk-rs │"); + eprintln!(" │ ╰─────────────────────────────────────────────────────────────────╯"); + } + Ok(artifacts) }); let thread_stderr = thread::spawn(move || { diff --git a/integration-tests/tests/abi/json_schema.rs b/integration-tests/tests/abi/json_schema.rs index f7f43c8e..2d9385f4 100644 --- a/integration-tests/tests/abi/json_schema.rs +++ b/integration-tests/tests/abi/json_schema.rs @@ -701,3 +701,78 @@ fn test_schema_complex() -> testresult::TestResult { Ok(()) } + +#[test] +#[named] +fn test_schema_vec_of_custom_types() -> testresult::TestResult { + // This test demonstrates the proper way to define custom types that can be used + // in Vec return types. Without #[near(serializers = [json])], the ABI generation + // would fail with "the trait bound `Item: JsonSchema` is not satisfied" + let abi_root = generate_abi! { + use near_sdk::near; + + #[near(serializers = [json])] + pub struct Item { + pub id: u32, + pub name: String, + } + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract {} + + #[near] + impl Contract { + pub fn get_items(&self) -> Vec { + vec![] + } + } + }; + + assert_eq!(abi_root.body.functions.len(), 2); + let function = &abi_root.body.functions[1]; + assert_eq!(function.name, "get_items"); + + // Verify the return type is an array of Item references + let return_schema = function.result.as_ref().unwrap().json_schema(); + let expected_schema: Schema = serde_json::from_str( + r#" + { + "type": "array", + "items": { + "$ref": "#/definitions/Item" + } + } + "#, + )?; + assert_eq!(return_schema.type_schema, expected_schema); + + // Verify the Item definition is in the schema definitions + let item_schema: Schema = serde_json::from_str( + r#" + { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "type": "string" + } + } + } + "#, + )?; + assert_eq!( + abi_root.body.root_schema.definitions["Item"], + item_schema + ); + + Ok(()) +}