Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-validator

Typed fluent validation rules for Pudu

0.1.1Apache-2.01

InstallClose

Selection.pudu

Pudu72 lines2.5 KB

GitHub ↗
1/** @Validation.Selection.Paths — relates requested and concrete property paths */2module PuduLangValidator.Selection34import Std.Text as Text56/// Whether a request selects this concrete property or one of its descendants.7export fn covers(requested: Str, path: Str) -> Bool {8  match scan(requested, path) {9    case Some((requestEnd, pathEnd)) => {10      let chars = path.chars()11      requestEnd == requested.length() && boundary(&chars, pathEnd)12    }13    case None => false14  }15}1617/// Whether a concrete rule and a request meet at a path boundary.18export fn overlaps(requested: Str, path: Str) -> Bool {19  match scan(requested, path) {20    case Some((requestEnd, pathEnd)) => {21      let requestedChars = requested.chars()22      let pathChars = path.chars()23      (requestEnd == requestedChars.length() && boundary(&pathChars, pathEnd)) ||24      (pathEnd == pathChars.length() && boundary(&requestedChars, requestEnd))25    }26    case None => false27  }28}2930/// Return a requested path relative to a concrete child prefix.31export fn relative(requested: Str, prefix: Str) -> Option[Str] {32  let positions = scan(requested, prefix) ?33  let requestEnd = positions[0]34  let prefixEnd = positions[1]35  if prefixEnd != prefix.length() { return None }36  let chars = requested.chars()37  if requestEnd == chars.length() { return Some("") }38  if chars[requestEnd] == '.' { Some(Text.drop(requested, requestEnd + 1)) } else { None }39}4041/// Whether the next unmatched character starts a child segment.42fn boundary(chars: &Array[Char], index: Int) -> Bool {43  index == chars.length() || chars[index] == '.' || chars[index] == '['44}4546/// Consume matching characters and one bracket value per empty index.47fn scan(requested: Str, path: Str) -> Option[(Int, Int)] {48  let requestChars = requested.chars()49  let pathChars = path.chars()50  var requestAt = 051  var pathAt = 052  while requestAt < requestChars.length() && pathAt < pathChars.length() {53    if requestChars[requestAt] == '[' && requestAt + 1 < requestChars.length() && requestChars[requestAt + 1] == ']' {54      if pathChars[pathAt] != '[' { return None }55      pathAt = pathAt + 156      let first = pathAt57      while pathAt < pathChars.length() && pathChars[pathAt] != ']' {58        if pathChars[pathAt] == '[' { return None }59        pathAt = pathAt + 160      }61      if pathAt == first || pathAt == pathChars.length() { return None }62      requestAt = requestAt + 263      pathAt = pathAt + 164    } else {65      if requestChars[requestAt] != pathChars[pathAt] { return None }66      requestAt = requestAt + 167      pathAt = pathAt + 168    }69  }70  Some((requestAt, pathAt))71}72