Skip to content

Paths

KForm uses “paths” to point to data within a form. Consider the schema presented in the previous section:

Schema of a form for purchasing bus tickets
val BusTripFormSchema = ClassSchema {
    BusTripForm::email { StringSchema() }
    BusTripForm::passengers {
        TableSchema {
            ClassSchema {
                Passenger::name { StringSchema() }
                Passenger::age { NullableSchema { IntSchema() } }
            }
        }
    }
}

Paths in KForm are similar to UNIX paths, for example:

  • /: points to the root value of the form (i.e. to an instance of BusTripForm).
  • /email: points to the email field of the root value (paths starting with / are “absolute”, i.e. they point to a specific field starting from the root).
  • /passengers/0/name: points to the name of the passenger with id 0 within the table instance at /passengers.
  • email or ./email: both paths are equivalent, they point to an email field relative to ., the “current path”.
  • ../age: if our “current path” is /passengers/0/name, ../age points to the age field of the same passenger (.. is the parent of the current path).
  • /passengers/*/name: points to the names of each passenger (the * symbol is a wildcard that matches any identifier).
  • /passengers/**: points to /passengers and all its descendants (the ** symbol is a “resursive wildcard” that matches any path).
  • /passengers/-: points to the “end” of the table instance at /passengers, i.e. to a position after the last element of the collection.

Types of paths

KForm represents paths using the following classes:

  • Path: generic path type, representing both relative and absolute paths. Example construction: Path("../age")
  • AbsolutePath: subtype of Path that is always absolute (i.e. always starts with /). Example construction: AbsolutePath("/passengers").

Although instances of these classes will be used throughout most KForm APIs, when receiving paths as arguments, most APIs will also accept Strings.

Useful operations

  • Path.equals: returns whether two paths are equal. Two paths are equal if they resolve (see below) to the same path. As an example, the following is true:

    Path("/passengers/1/../0/name") == AbsolutePath("/passengers/0/name")
    
  • Path.resolve, AbsolutePath.resolve: resolves paths against one another. The following assertions are all correct:

    assertEquals(Path("/passengers/0/./name/../age").resolve(), Path("/passengers/0/age"))
    assertEquals(
        AbsolutePath("/passengers/0/name").resolve("../age"),
        AbsolutePath("/passengers/0/age"),
    )
    assertEquals(Path("0/name").resolve("../../../email"), Path("../email"))
    

  • AbsolutePath.matches: returns whether a path “matches” against another path. The following is true, and so is the opposite:

    AbsolutePath("/passengers/**").matches("/passengers/0/name")
    
  • AbsolutePath.contains: returns whether a path contains another path. The following is true, but not the opposite:

    AbsolutePath("/passengers/**").contains("/passengers/0/name")
    
  • AbsolutePath.relativeTo: returns a path relative to another path. The following assertion is correct:

    assertEquals(
        AbsolutePath("/passengers/0/name").relativeTo("/passengers/0/age"),
        Path("../name"),
    )
    

String representation

As previously mentioned, most KForm APIs accept Strings as arguments where paths are expected. In rare scenarios, the following rules might have to be taken into consideration:

  • String paths use the character ~ as an escape character. Use ~ to escape all instances of ~ or / in an identifier, or identifiers that are exactly ., .., *, **, or -.

    For example, the path /a~//b~~c/~./~* contains four identifier fragments: "a/", "b~c", ".", and "*".

  • “Empty” identifiers are represented by a trailing separator (/). E.g., the path /a//b// has four identifier fragments, in order: "a", "", "b", and "".