Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

valkyrie:0.1.1 #593

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/preview/valkyrie/0.1.1/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# [unreleased](https://github.com/jamesxX/valakyrie/releases/tags/)
## Added

## Removed

## Changed

---

# [v0.1.1](https://github.com/jamesxX/valakyrie/releases/tags/v0.1.1)
## Changed
- fixed syntax error in Typst 0.11+ because of internal context type

---

# [v0.1.0](https://github.com/jamesxX/valakyrie/releases/tags/v0.1.0)
Initial Release
674 changes: 674 additions & 0 deletions packages/preview/valkyrie/0.1.1/LICENSE

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions packages/preview/valkyrie/0.1.1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# The `Valkyrie` Package
<div align="center">Version 0.1.1</div>

This package implements type validation, and is targetted mainly at package and template developers. The desired outcome is that it becomes easier for the programmer to quickly put a package together without spending a long time on type safety, but also to make the usage of those packages by end-users less painful by generating useful error messages.

## Example Usage
```typ
#import "@preview/valkyrie:0.1.1" as z

#let my-schema = z.dictionary(
should-be-string: z.string(),
complicated-tuple: z.tuple(
z.email(),
z.ip(),
z.either(
z.string(),
z.number(),
),
),
)

#z.parse(
(
should-be-string: "This doesn't error",
complicated-tuple: (
"[email protected]",
// Error: Schema validation failed on argument.complicated-tuple.1:
// String must be a valid IP address
"NOT AN IP",
1,
),
),
my-schema,
)
```
Binary file added packages/preview/valkyrie/0.1.1/docs/manual.pdf
Binary file not shown.
72 changes: 72 additions & 0 deletions packages/preview/valkyrie/0.1.1/src/base-type.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#import "ctx.typ": z-ctx

/// Asserts the presence of the magic number on the given object.
///
/// - arg (any):
/// - scope (scope): Array of strings containing information for error generation.
/// -> none
#let assert-base-type(arg, scope: ("arguments",)) = {
assert("valkyrie-type" in arg,
message: "Invalid valkyrie type in " + scope.join(".")
)
}

/// Asserts the presence of the magic number on an array of object.
///
/// - arg (any):
/// - scope (scope): Array of strings containing information for error generation.
/// -> none
#let assert-base-type-array(arg, scope: ("arguments",)) = {
for (name, value) in arg.enumerate() {
assert-base-type(value, scope: (..scope, str(name)))
}
}

/// Asserts the presence of the magic number in a dictionary of object.
///
/// - arg (any):
/// - scope (scope): Array of strings containing information for error generation.
/// -> none
#let assert-base-type-dictionary(arg, scope: ("arguments",)) = {
for (name, value) in arg {
assert-base-type(value, scope: (..scope, name))
}
}

/// Asserts the presence of the magic number in an argument of object.
///
/// - arg (any):
/// - scope (scope): Array of strings containing information for error generation.
/// -> none
#let assert-base-type-arguments(arg, scope: ("arguments",)) = {
for (name, value) in arg.named() {
assert-base-type(value, scope: (..scope, name))
}

for (pos, value) in arg.pos().enumerate() {
assert-base-type(value, scope: (..scope, "[" + pos + "]"))
}
}

/// Schema generator. Provides default values for when defining custom types.
#let base-type() = (
valkyrie-type: true,
assert-type: (self, it, scope:(), ctx: z-ctx(), types: ()) => {
if type(it) not in types {
(self.fail-validation)(self, it, scope: scope, ctx: ctx,
message: "Expected " + types.join(", ", last: " or ") + ". Got " + type(it))
return false
}

true
},
validate: (self, it, scope: (), ctx: z-ctx()) => it,
fail-validation: (self, it, scope: (), ctx: z-ctx(), message: "") => {
let display = "Schema validation failed on " + scope.join(".")
if message.len() > 0 { display += ": " + message}
ctx.outcome = display
if not ctx.soft-error {
assert(false, message: display)
}
}
)
13 changes: 13 additions & 0 deletions packages/preview/valkyrie/0.1.1/src/ctx.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#let ctx-proto = (
strict: false,
soft-error: false,
// TODO(james)
coerce: false,
)

/// Appends options to a context. Used for setting the context of child parses.
///
/// - parent (ctx, none): Current context (if present), to which contextual
/// flags passed in variadic arguments are appended.
/// - ..args (arguments): Variadic contextual flags to set. Positionala rguments are discarded.
#let z-ctx(parent: (:), ..args) = ctx-proto + parent + args.named()
46 changes: 46 additions & 0 deletions packages/preview/valkyrie/0.1.1/src/lib.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#import "types.typ": *
#import "ctx.typ": z-ctx
#import "base-type.typ" as advanced

/// This is the main function for validating an object against a schema. *WILL* return the given
/// object after validation if successful, or none and *MAY* throw a failed assertion error.
///
/// - object (any): Object to validate against provided schema. Object *SHOULD* statisfy the schema
/// requirements. An error *MAY* be produced if not.
/// - schema (schema): Schema against which `object` is validated. *MUST* be a valid valkyrie schema
/// type.
/// - ctx (ctx): ctx passed to schema validator function, containing flags that *MAY* alter
/// behaviour.
/// - scope (scope): An array of strings used to generate the string representing the location of a
/// failed requirement within `object`. *MUST* be an array of strings of length greater than or
/// equal to `1`.
/// -> any, none
#let parse(
object, schema,
ctx: z-ctx(),
scope: ("argument",),
) = {
// don't expose to external
import "base-type.typ": assert-base-type

// Validate named arguments
assert-base-type(schema, scope: scope)

// Validate arguments per schema
object = (schema.validate)(
schema,
ctx: ctx,
scope: scope,
object,
)

// Require arguments match schema exactly in strict mode
if ctx.strict {
for (argument-name, argument-value) in object {
assert(argument-name in schema, message: "Unexpected argument " + argument-name)
}
}

object
}

7 changes: 7 additions & 0 deletions packages/preview/valkyrie/0.1.1/src/types.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "types/any.typ": *
#import "types/array.typ": *
#import "types/dictionary.typ": *
#import "types/logical.typ": *
#import "types/number.typ": *
#import "types/string.typ": *
#import "types/tuple.typ": *
47 changes: 47 additions & 0 deletions packages/preview/valkyrie/0.1.1/src/types/any.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#import "../base-type.typ": base-type, assert-base-type
#import "../ctx.typ": z-ctx

/// Validation schema representing all types. *SHOULD* never produce an error.
///
/// - name (internal):
/// - default (any, none): Default value to validate is none is provided.
/// - custom (function): Function that maps an input to an output. If the function returns `none`,
/// then an error *WILL* be generated using `custom-error`.
/// - custom-error (string): Error to return if custom function returns none.
/// - transform (function): Function that maps an input to an output, called after validation.
/// -> schema
#let any(
name: "any",
default: none,
custom: none,
custom-error: auto,
transform: it => it,
) = {
assert(type(custom) in (function, type(none)), message: "Custom must be a function")
assert(type(custom-error) in (str, type(auto)), message: "Custom-error must be a string")
assert(type(transform) == function,
message: "Transform must be a function that maps an input to an output",
)

base-type() + (
name: name,
default: default,
custom: custom,
custom-error: custom-error,
transform: transform,

validate: (self, it, ctx: z-ctx(), scope: ()) => {
// Default value
if (it == none){ it = self.default }

// Custom
if self.custom != none and not (self.custom)(it) {
let message = "Failed on custom check: " + repr(self.custom)
if ( self.custom-error != auto ){ message = self.custom-error }
return (self.fail-validation)(self, it, ctx: ctx, scope: scope, message: message)
}

(self.transform)(it)
}
)
}
136 changes: 136 additions & 0 deletions packages/preview/valkyrie/0.1.1/src/types/array.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#import "../base-type.typ": base-type, assert-base-type
#import "../ctx.typ": z-ctx
#import "any.typ": any

/// Valkyrie schema generator for array types. Array entries are validated by a single schema. For
/// arrays with positional requirements, see @@tuple.
///
/// - name (internal):
/// - default (array, none): Default value to set if no value is provided. *MUST* itself pass
/// validation.
/// - min (integer, none): If not none, the minimum array length that satisfies the validation.
/// *MUST* be a positive integer. The program is *ILL-FORMED* if `min` is greater than `max`.
/// - max (integer, none): If not none, the maximum array length that satisfies the validation.
/// *MUST* be a positive integer. The program is *ILL-FORMED* if `max` is less than `min`.
/// - length (integer, auto): If not auto, the exact array length that satisfies validation. *MUST*
/// be a positiive integer. The program *MAY* be *ILL-FORMED* is concurrently set with either
/// `min` or `max`.
/// - custom (function, none): If not none, a function that, if itself returns none, will produce
/// the error set by `custom-error`.
/// - custom-error (string, none): If set, the error produced upon failure of `custom`.
/// - transform (function): a mapping function called after validation.
/// - ..args (schema, none): Variadic positional arguments of length `0` or `1`. *SHOULD* not
/// contain named arguments. If no arguments are given, schema defaults to array of @@any
/// -> schema
#let array(
name: "array",
default: (),
min: none,
max: none,
length: auto,
custom: none,
custom-error: auto,
transform: it=>it,
..args
) = {
// assert default is array
assert(type(min) in (int, type(none)), message: "Minimum length must be an integer")
if min != none { assert(min >= 0, message: "Minimum length must be a positive integer") }

assert(type(max) in (int, type(none)), message: "Maximum length must be an integer")
if max != none { assert(max >= 0, message: "Maximum length must be a positive integer") }

assert(type(length) in (int, type(auto)), message: "Length must be an integer")
if length != auto { assert(length >= 0, message: "Maximum length must be a positive integer") }

assert(type(custom) in (function, type(none)), message: "Custom must be a function")
assert(type(custom-error) in (str, type(auto)), message: "Custom-error must be a string")
assert(type(transform) == function,
message: "Transform must be a function that takes a single string and return a string",
)

let positional-arguments = args.pos()

let valkyrie-array-typ
if positional-arguments.len() < 1 {
valkyrie-array-typ = any()
} else {
valkyrie-array-typ = positional-arguments.first()
assert-base-type(valkyrie-array-typ, scope: ("arguments",))
}

let name = name + "[" + (valkyrie-array-typ.name) +"]"

base-type() + (
name: name,
default: default,
min: min,
max: max,
length: length,
valkyrie-array-typ: valkyrie-array-typ,
custom: custom,
custom-error: custom-error,
transform: transform,
validate: (self, it, ctx: z-ctx(), scope: ()) => {
// Default value
if it == none { it = self.default }

// Array must be an array
if not (self.assert-type)(self, it, scope: scope, ctx: ctx, types: (type(()),)){
return none
}

// Minimum length
if (self.min != none) and (it.len() < self.min) {
return (self.fail-validation)(
self,
it,
ctx: ctx,
scope: scope,
message: "Array length less than specified minimum of " + str(self.min),
)
}

// Minimum length
if (self.max != none) and (it.len() > self.max) {
return (self.fail-validation)(
self,
it,
ctx: ctx,
scope: scope,
message: "Array length greater than specified maximum of " + str(self.max),
)
}

// Exact length
if (self.length != auto) and (it.len() != self.length) {
return (self.fail-validation)(
self,
it,
ctx: ctx,
scope: scope,
message: "Array length must exactly equal " + str(self.length),
)
}

// Check elements
for (key, value) in it.enumerate(){
it.at(key) = (valkyrie-array-typ.validate)(
valkyrie-array-typ,
value,
ctx: ctx,
scope: (..scope, str(key)),
)
}

// Custom
if ( self.custom != none ) and ( not (self.custom)(it) ){
let message = "Failed on custom check: " + repr(self.custom)
if ( self.custom-error != auto ){ message = self.custom-error }
return (self.fail-validation)(self, it, ctx: ctx, scope: scope, message: message)
}

(self.transform)(it)
}
)
}
Loading
Loading