use pushkin_compiler::{compile, CompileError, CompileRequest, Target};
const ALL_TARGETS: [Target; 4] = [Target::Zod, Target::Pydantic, Target::Rust, Target::Sql];
fn request(contract_name: &str, schema_json: &str, target: Target) -> CompileRequest {
CompileRequest {
contract_name: contract_name.to_owned(),
schema_json: schema_json.to_owned(),
target,
epoch: 1,
}
}
fn object_schema(properties_json: &str) -> String {
format!(
r#"{{"type":"object","properties":{properties_json},"required":[],"additionalProperties":false}}"#
)
}
#[test]
fn property_named_like_structural_keyword_compiles() {
let schema = object_schema(
r#"{
"not":{"type":"string"},
"allOf":{"type":"string"},
"anyOf":{"type":"string"},
"oneOf":{"type":"string"},
"patternProperties":{"type":"string"},
"prefixItems":{"type":"string"}
}"#,
);
for target in ALL_TARGETS {
compile(&request("remediation", &schema, target)).unwrap();
}
}
#[test]
fn structural_keyword_inside_property_spec_still_rejects() {
let schema = object_schema(r#"{"value":{"type":"string","allOf":[]}}"#);
let error = compile(&request("remediation", &schema, Target::Zod)).unwrap_err();
let message = error.to_string();
assert!(matches!(error, CompileError::Unrepresentable { .. }));
assert!(message.contains("allOf"), "{message}");
}
#[test]
fn structural_keyword_at_top_level_still_rejects() {
let schema = r#"{"type":"object","properties":{},"not":{},"additionalProperties":false}"#;
let error = compile(&request("remediation", schema, Target::Pydantic)).unwrap_err();
assert!(matches!(error, CompileError::Unrepresentable { .. }));
}
#[test]
fn property_name_outside_identifier_pattern_rejects() {
for name in ["first name", "first-name", "1st", "", "naïve", "a.b"] {
let schema = object_schema(&format!(r#"{{"{name}":{{"type":"string"}}}}"#));
for target in ALL_TARGETS {
let error = compile(&request("remediation", &schema, target)).unwrap_err();
let message = error.to_string();
assert!(
matches!(error, CompileError::InvalidSchema { .. }),
"property {name:?} must reject as invalid schema, got: {message}"
);
assert!(message.contains("[A-Za-z_][A-Za-z0-9_]*"), "{message}");
}
}
}
#[test]
fn dollar_ref_property_name_rejects_as_identifier_not_structural() {
let schema = object_schema(r#"{"$ref":{"type":"string"}}"#);
let error = compile(&request("remediation", &schema, Target::Rust)).unwrap_err();
let message = error.to_string();
assert!(
matches!(error, CompileError::InvalidSchema { .. }),
"'$ref' as a property NAME is an identifier problem, not a structural construct: {message}"
);
assert!(message.contains("[A-Za-z_][A-Za-z0-9_]*"), "{message}");
}
#[test]
fn contract_name_outside_identifier_pattern_rejects() {
let schema = object_schema(r#"{"value":{"type":"string"}}"#);
for contract_name in ["user profile", "user-profile", "2user", "", "user;drop"] {
for target in ALL_TARGETS {
let error = compile(&request(contract_name, &schema, target)).unwrap_err();
let message = error.to_string();
assert!(
matches!(error, CompileError::InvalidSchema { .. }),
"contract {contract_name:?} must reject, got: {message}"
);
assert!(message.contains("[A-Za-z_][A-Za-z0-9_]*"), "{message}");
}
}
}
#[test]
fn valid_identifier_names_compile_across_targets() {
let schema = object_schema(r#"{"_private":{"type":"string"},"name2":{"type":"string"}}"#);
for target in ALL_TARGETS {
compile(&request("user_create", &schema, target)).unwrap();
}
}