///|
pub struct ZoneRecord {
owner_value : DomainName
ttl_value : Ttl
class_value : String
type_value : String
data_value : RecordData
explicit_ttl_value : Bool
span_value : SourceSpan
} derive(Eq, Debug)
///|
fn zone_record_error(
code : ZoneErrorCode,
message : String,
token : ZoneToken,
) -> ZoneError {
ZoneError::new(code, message, token.span())
}
///|
fn token_looks_numeric(token : ZoneToken) -> Bool {
token.text().length() > 0 &&
token.text()[0].to_int() >= 48 &&
token.text()[0].to_int() <= 57
}
///|
fn token_looks_class(token : ZoneToken) -> Bool {
let value = ascii_lower(token.text())
value == "in" || value == "ch" || value == "hs"
}
///|
fn resolve_record_owner(
token : ZoneToken,
origin : DomainName,
) -> Result[DomainName, ZoneError] {
match resolve_zone_name(token.text(), origin) {
Ok(value) => Ok(value)
Err(error) =>
Err(ZoneError::new(InvalidDomainName, error.message(), token.span()))
}
}
///|
fn statement_span(tokens : Array[ZoneToken]) -> SourceSpan {
let first = tokens[0].span()
let last = tokens[tokens.length() - 1].span()
SourceSpan::range(
first.start_line(),
first.start_column(),
last.end_line(),
last.end_column(),
)
}
///|
pub fn parse_zone_record(
statement : ZoneStatement,
origin : DomainName,
inherited_owner? : DomainName? = None,
inherited_ttl? : Ttl? = None,
) -> Result[ZoneRecord, ZoneError] {
let tokens = statement.tokens()
if tokens.length() == 0 {
return Err(
ZoneError::new(
MissingRecordField,
"zone record statement is empty",
SourceSpan::point(0, 0),
),
)
}
if !origin.is_absolute() {
return Err(
zone_record_error(
RelativeNameWithoutOrigin,
"zone record requires an absolute origin",
tokens[0],
),
)
}
let owner_omitted = tokens[0].span().start_column() > 0
let mut index = 0
let owner = if owner_omitted {
match inherited_owner {
Some(value) => value
None =>
return Err(
zone_record_error(
MissingRecordField,
"indented record has no owner to inherit",
tokens[0],
),
)
}
} else {
index = 1
match resolve_record_owner(tokens[0], origin) {
Ok(value) => value
Err(error) => return Err(error)
}
}
let mut ttl = inherited_ttl
let mut explicit_ttl = false
let mut saw_class = false
let mut optional_fields = 0
while index < tokens.length() && optional_fields < 2 {
if token_looks_numeric(tokens[index]) {
if explicit_ttl {
return Err(
zone_record_error(
InvalidRecordData,
"record contains more than one TTL field",
tokens[index],
),
)
}
ttl = match parse_ttl(tokens[index].text()) {
Ok(value) => Some(value)
Err(error) =>
return Err(
ZoneError::new(InvalidTtl, error.message(), tokens[index].span()),
)
}
explicit_ttl = true
index = index + 1
optional_fields = optional_fields + 1
} else if token_looks_class(tokens[index]) {
if saw_class {
return Err(
zone_record_error(
InvalidRecordClass,
"record contains more than one class field",
tokens[index],
),
)
}
if ascii_lower(tokens[index].text()) != "in" {
return Err(
zone_record_error(
InvalidRecordClass,
"version 0.1.0 supports only the IN record class",
tokens[index],
),
)
}
saw_class = true
index = index + 1
optional_fields = optional_fields + 1
} else {
break
}
}
let effective_ttl = match ttl {
Some(value) => value
None =>
return Err(
zone_record_error(
MissingRecordField,
"record has no explicit or inherited TTL",
tokens[0],
),
)
}
if index >= tokens.length() {
return Err(
zone_record_error(
MissingRecordField,
"record type is missing",
tokens[tokens.length() - 1],
),
)
}
let type_token = tokens[index]
index = index + 1
let fields : Array[ZoneToken] = []
while index < tokens.length() {
fields.push(tokens[index])
index = index + 1
}
let data = match parse_record_data(type_token, fields, origin) {
Ok(value) => value
Err(error) => return Err(error)
}
Ok({
owner_value: owner,
ttl_value: effective_ttl,
class_value: "IN",
type_value: uppercase_ascii(type_token.text()),
data_value: data,
explicit_ttl_value: explicit_ttl,
span_value: statement_span(tokens),
})
}
///|
pub fn ZoneRecord::owner(self : ZoneRecord) -> DomainName {
self.owner_value
}
///|
pub fn ZoneRecord::ttl(self : ZoneRecord) -> Ttl {
self.ttl_value
}
///|
pub fn ZoneRecord::record_class(self : ZoneRecord) -> String {
self.class_value
}
///|
pub fn ZoneRecord::record_type(self : ZoneRecord) -> String {
self.type_value
}
///|
pub fn ZoneRecord::data(self : ZoneRecord) -> RecordData {
self.data_value
}
///|
pub fn ZoneRecord::ttl_was_explicit(self : ZoneRecord) -> Bool {
self.explicit_ttl_value
}
///|
pub fn ZoneRecord::span(self : ZoneRecord) -> SourceSpan {
self.span_value
}