Documentation
¶
Overview ¶
Package predicate parses and evaluates a subset of NSPredicate syntax for declarative device management activations.
Design ¶
The engine validates activation predicates at upload, and the simulator evaluates them against @property values and @status items. Unsupported forms return an explicit error. Membership resolution determines which declarations a device sees; predicate evaluation determines whether an activation applies its configurations.
# Grammar Keywords and operators are case-insensitive. Whitespace between tokens is insignificant.
predicate := or
or := and { ("OR" | "||") and }
and := not { ("AND" | "&&") not }
not := ("NOT" | "!") not | primary
primary := "(" or ")" | "TRUEPREDICATE" | "FALSEPREDICATE" | comparison
comparison := operand [ "[c]" ] op [ "[c]" ] operand
operand := "@property" "(" key ")" | "@status" "(" path ")" | literal
literal := string | number | "TRUE" | "FALSE" | "YES" | "NO"
| "NULL" | "NIL" | "{" [ literal { "," literal } ] "}"
op := "==" | "=" | "!=" | "<>" | "<" | "<=" | ">" | ">="
| "IN" | "CONTAINS" | "BEGINSWITH" | "ENDSWITH"
key, path := [A-Za-z0-9_.-]+ | string
string := ( "'" ... "'" | '"' ... '"' ) with escapes \\ \' \" \n \r \t \uXXXX
number := [+-] digits [ "." digits ] [ ("e" | "E") [+-] digits ]
The [c] modifier requests case-insensitive string comparison. Apple writes it after the operator (`==[c]`); the modifier is also accepted immediately before the operator. It is permitted only on ==, !=, IN, CONTAINS, BEGINSWITH and ENDSWITH.
Unsupported constructs ¶
The following NSPredicate features are recognised and rejected with a *SyntaxError whose Err is ErrUnsupported and whose Msg names the construct: SELF, bare key paths outside @property or @status, %K and %@ format arguments, $variables, MATCHES, LIKE, BETWEEN, ANY, ALL, NONE, SOME, SUBQUERY, FUNCTION and any identifier followed by "(", arithmetic operators, the [d], [cd] and [n] modifiers, and CAST.
Evaluation ¶
A missing property or status item evaluates to nil. The == and != operators compare nil like any other value, so nil == nil is true. Ordering, IN, CONTAINS, BEGINSWITH and ENDSWITH involving nil are false. Integers and floats from the environment promote to float64, strings compare lexically, and booleans support only == and !=. Mixing a string with a number, or a boolean with anything else, is a type mismatch and Eval returns an error wrapping ErrType. IN requires an aggregate on its right-hand side, either a `{...}` literal or a slice supplied by the environment. CONTAINS, BEGINSWITH and ENDSWITH require strings on both sides. TRUEPREDICATE and FALSEPREDICATE are constants.
References ¶
- Decision record 0020: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/research/decisions/0020-ddm-engine-membership-and-storage.md
- Decision record 0024: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/research/decisions/0024-simulator-ddm-client-and-predicates.md
- Threat model: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/security/threat-model.md (Admin upload of declarations row)
- End-to-end scenarios: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/testing/e2e-scenarios.md (E2E-009)
- Apple: https://developer.apple.com/documentation/devicemanagement/leveraging-the-declarative-management-data-model-to-scale-devices
- Apple: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html
- Schema: third_party/device-management/declarative/declarations/activations/simple.yaml (Predicate)
- Schema: third_party/device-management/declarative/declarations/management/properties.yaml (@property values)
- Schema: third_party/device-management/declarative/declarations/declarationbase.yaml (Info.Predicate reason code)
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrSyntax = errors.New("predicate: syntax error") ErrUnsupported = errors.New("predicate: unsupported construct") ErrType = errors.New("predicate: type mismatch") )
Sentinel errors. Errors returned by Parse and Validate wrap ErrSyntax or ErrUnsupported and are always of type *SyntaxError. Errors returned by Eval wrap ErrType when operand types are incompatible.
Functions ¶
Types ¶
type Env ¶
type Env interface {
// Property returns the activation property for key.
Property(key string) (any, bool)
// Status returns the status item at path.
Status(path string) (any, bool)
}
Env supplies the values that @property and @status references resolve to. A reference whose lookup reports false evaluates to nil. Values may be nil, bool, string, any integer or float type, json.Number, or a slice of those.
type MapEnv ¶
type MapEnv struct {
// Properties resolves @property(key).
Properties map[string]any
// StatusItems resolves @status(path).
StatusItems map[string]any
}
MapEnv is an Env backed by two maps. A nil map simply has no entries.
type Predicate ¶
type Predicate struct {
// contains filtered or unexported fields
}
Predicate is a parsed activation predicate.
func MustParse ¶
MustParse is like Parse but panics on error. It is intended for predicates fixed at compile time.
func Parse ¶
Parse parses a predicate format string. The returned error wraps ErrSyntax or ErrUnsupported and is of type *SyntaxError.
Example ¶
package main
import (
"fmt"
"github.com/deploymenttheory/go-apple-dm/mdmprotocol/ddm/predicate"
)
func main() {
p, err := predicate.Parse(
`(@property(shard) <= 75) and @status(device.identifier.serial-number) beginswith 'ZYXW'`,
)
if err != nil {
fmt.Println("parse:", err)
return
}
env := predicate.MapEnv{
Properties: map[string]any{"shard": 40},
StatusItems: map[string]any{"device.identifier.serial-number": "ZYXW4321"},
}
ok, err := p.Eval(env)
fmt.Println(p)
fmt.Println(ok, err)
_, err = predicate.Parse(`SELF.name LIKE 'a*'`)
fmt.Println(err)
}
Output: @property(shard) <= 75 AND @status(device.identifier.serial-number) BEGINSWITH 'ZYXW' true <nil> predicate: unsupported construct at offset 0: SELF is not supported
func (*Predicate) Eval ¶
Eval evaluates the predicate against env. A nil env resolves every reference to nil. Errors wrap ErrType.
type Properties ¶
Properties is an Env backed by a map of activation properties. Status lookups are never found.
type SyntaxError ¶
type SyntaxError struct {
// Offset is the byte offset in the source at which the problem was found.
Offset int
// Msg describes the problem and, for unsupported constructs, names the
// construct.
Msg string
// Err is ErrSyntax or ErrUnsupported.
Err error
}
SyntaxError describes why a predicate failed to parse.
func (*SyntaxError) Unwrap ¶
func (e *SyntaxError) Unwrap() error
Unwrap returns the sentinel error.