Skip to content

Commit

Permalink
AVRO-3837: [Rust] Disallow invalid namespaces for the Rust binding (#…
Browse files Browse the repository at this point in the history
…2456)

* AVRO-3837: [Rust] Disallow invalid namespaces for the Rust binding

* AVRO-3837: [Rust] Add few more test cases

Signed-off-by: Martin Tzvetanov Grigorov <mgrigorov@apache.org>

---------

Signed-off-by: Martin Tzvetanov Grigorov <mgrigorov@apache.org>
Co-authored-by: Martin Tzvetanov Grigorov <mgrigorov@apache.org>
  • Loading branch information
sarutak and martin-g authored Aug 21, 2023
1 parent a12a7e4 commit 699cfee
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 7 deletions.
3 changes: 3 additions & 0 deletions lang/rust/avro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ pub enum Error {
#[error("Invalid schema name {0}. It must match the regex '{1}'")]
InvalidSchemaName(String, &'static str),

#[error("Invalid namespace {0}. It must match the regex '{1}'")]
InvalidNamespace(String, &'static str),

#[error("Duplicate enum symbol {0}")]
EnumSymbolDuplicate(String),

Expand Down
137 changes: 130 additions & 7 deletions lang/rust/avro/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ lazy_static! {
Regex::new(r"^((?P<namespace>([A-Za-z_][A-Za-z0-9_\.]*)*)\.)?(?P<name>[A-Za-z_][A-Za-z0-9_]*)$").unwrap();

static ref FIELD_NAME_R: Regex = Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap();

static ref NAMESPACE_R: Regex = Regex::new(r"^([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?$").unwrap();
}

/// Represents an Avro schema fingerprint
Expand Down Expand Up @@ -258,15 +260,26 @@ impl Name {
_ => None,
};

let namespace = namespace_from_name
.or_else(|| {
complex
.string("namespace")
.or_else(|| enclosing_namespace.clone())
})
.filter(|ns| !ns.is_empty());

if let Some(ref ns) = namespace {
if !NAMESPACE_R.is_match(ns) {
return Err(Error::InvalidNamespace(
ns.to_string(),
NAMESPACE_R.as_str(),
));
}
}

Ok(Self {
name: type_name.unwrap_or(name),
namespace: namespace_from_name
.or_else(|| {
complex
.string("namespace")
.or_else(|| enclosing_namespace.clone())
})
.filter(|ns| !ns.is_empty()),
namespace,
})
}

Expand Down Expand Up @@ -5338,4 +5351,114 @@ mod tests {
assert_eq!(d.inner_record.unwrap().a, "foo".to_string());
Ok(())
}

#[test]
fn test_avro_3837_disallow_invalid_namespace() -> TestResult {
// Valid namespace #1 (Single name portion)
let schema_str = r#"
{
"name": "record1",
"namespace": "ns1",
"type": "record",
"fields": []
}
"#;

let expected = r#"{"name":"ns1.record1","type":"record","fields":[]}"#;
let schema = Schema::parse_str(schema_str)?;
let canonical_form = schema.canonical_form();
assert_eq!(canonical_form, expected);

// Valid namespace #2 (multiple name portions).
let schema_str = r#"
{
"name": "enum1",
"namespace": "ns1.foo.bar",
"type": "enum",
"symbols": ["a"]
}
"#;

let expected = r#"{"name":"ns1.foo.bar.enum1","type":"enum","symbols":["a"]}"#;
let schema = Schema::parse_str(schema_str)?;
let canonical_form = schema.canonical_form();
assert_eq!(canonical_form, expected);

// Invalid namespace #1 (a name portion starts with dot)
let schema_str = r#"
{
"name": "fixed1",
"namespace": ".ns1.a.b",
"type": "fixed",
"size": 1
}
"#;

match Schema::parse_str(schema_str) {
Err(Error::InvalidNamespace(_, _)) => (),
other => return Err(format!("Expected Error::InvalidNamespace, got {other:?}").into()),
};

// Invalid namespace #2 (invalid character in a name portion)
let schema_str = r#"
{
"name": "record1",
"namespace": "ns1.a*b.c",
"type": "record",
"fields": []
}
"#;

match Schema::parse_str(schema_str) {
Err(Error::InvalidNamespace(_, _)) => (),
other => return Err(format!("Expected Error::InvalidNamespace, got {other:?}").into()),
};

// Invalid namespace #3 (a name portion starts with a digit)
let schema_str = r#"
{
"name": "fixed1",
"namespace": "ns1.1a.b",
"type": "fixed",
"size": 1
}
"#;

match Schema::parse_str(schema_str) {
Err(Error::InvalidNamespace(_, _)) => (),
other => return Err(format!("Expected Error::InvalidNamespace, got {other:?}").into()),
};

// Invalid namespace #4 (a name portion is missing - two dots in a row)
let schema_str = r#"
{
"name": "fixed1",
"namespace": "ns1..a",
"type": "fixed",
"size": 1
}
"#;

match Schema::parse_str(schema_str) {
Err(Error::InvalidNamespace(_, _)) => (),
other => return Err(format!("Expected Error::InvalidNamespace, got {other:?}").into()),
};

// Invalid namespace #5 (a name portion is missing - ends with a dot)
let schema_str = r#"
{
"name": "fixed1",
"namespace": "ns1.a.",
"type": "fixed",
"size": 1
}
"#;

match Schema::parse_str(schema_str) {
Err(Error::InvalidNamespace(_, _)) => (),
other => return Err(format!("Expected Error::InvalidNamespace, got {other:?}").into()),
};

Ok(())
}
}

0 comments on commit 699cfee

Please sign in to comment.