Skip to content

Commit

Permalink
Comments, Runtime -> JMESRuntime, Error renaming
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed May 31, 2021
1 parent 7fa8b3c commit a4ea9ab
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 109 deletions.
2 changes: 1 addition & 1 deletion Sources/JMESPath/Ast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public enum Comparator: Equatable {
case .greaterThan: self = .greaterThan
case .greaterThanOrEqual: self = .greaterThanOrEqual
default:
throw JMESPathError.syntaxError("Failed to parse comparison symbol")
throw JMESPathError.compileTime("Failed to parse comparison symbol")
}
}
}
17 changes: 7 additions & 10 deletions Sources/JMESPath/Error.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/// JMESPath error type.
///
/// Provides two errors, compile time and run time errors
public struct JMESPathError: Error, Equatable {
public static func syntaxError(_ message: String) -> Self { .init(value: .syntaxError(message))}
public static func invalidArguments(_ message: String) -> Self { .init(value: .invalidArguments(message))}
public static func invalidType(_ message: String) -> Self { .init(value: .invalidType(message))}
public static func invalidValue(_ message: String) -> Self { .init(value: .invalidValue(message))}
public static func unknownFunction(_ message: String) -> Self { .init(value: .unknownFunction(message))}
/// Error that occurred while compiling JMESPath
public static func compileTime(_ message: String) -> Self { .init(value: .compileTime(message))}
/// Error that occurred while running a search
public static func runtime(_ message: String) -> Self { .init(value: .runtime(message))}

private enum Internal: Equatable {
case syntaxError(String)
case invalidArguments(String)
case invalidType(String)
case invalidValue(String)
case unknownFunction(String)
case compileTime(String)
case runtime(String)
}

Expand Down
50 changes: 40 additions & 10 deletions Sources/JMESPath/Expression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation

/// JMES Expression
///
/// Holds a compiled JMES expression and allows you to search Json text or a structure already in memory
/// Holds a compiled JMES expression and allows you to search Json text or a type already in memory
public struct Expression {
let ast: Ast

Expand All @@ -14,21 +14,51 @@ public struct Expression {
return self.init(ast)
}

public func search(_ any: Any, runtime: Runtime = .init()) throws -> Any? {
return try runtime.interpret(JMESVariable(from: any), ast: self.ast).collapse()
/// Search JSON
///
/// - Parameters:
/// - any: JSON to search
/// - as: Swift type to return
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search<Value>(json: String, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value? {
return try search(json: json, runtime: runtime) as? Value
}

public func search(json: String, runtime: Runtime = .init()) throws -> Any? {
let value = try JMESVariable.fromJson(json)
return try runtime.interpret(value, ast: self.ast).collapse()
/// Search Swift type
///
/// - Parameters:
/// - any: Swift type to search
/// - as: Swift type to return
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search<Value>(_ any: Any, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value? {
return try search(any, runtime: runtime) as? Value
}

public func search<Value>(_ any: Any, as: Value.Type = Value.self, runtime: Runtime = .init()) throws -> Value? {
return try search(any, runtime: runtime) as? Value
/// Search JSON
///
/// - Parameters:
/// - any: JSON to search
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(json: String, runtime: JMESRuntime = .init()) throws -> Any? {
let value = try JMESVariable.fromJson(json)
return try runtime.interpret(value, ast: self.ast).collapse()
}

public func search<Value>(json: String, as: Value.Type = Value.self, runtime: Runtime = .init()) throws -> Value? {
return try search(json: json, runtime: runtime) as? Value
/// Search Swift type
///
/// - Parameters:
/// - any: Swift type to search
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(_ any: Any, runtime: JMESRuntime = .init()) throws -> Any? {
return try runtime.interpret(JMESVariable(from: any), ast: self.ast).collapse()
}

private init(_ ast: Ast) {
Expand Down
54 changes: 30 additions & 24 deletions Sources/JMESPath/Functions.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation

/// Function argument used in function signature to verify arguments
public indirect enum FunctionArgumentType {
case any
case null
Expand All @@ -14,6 +15,7 @@ public indirect enum FunctionArgumentType {
}

extension JMESVariable {
/// Is variable of a certain argument type
func isType(_ type: FunctionArgumentType) -> Bool {
switch (self, type) {
case (_, .any),
Expand All @@ -40,6 +42,7 @@ extension JMESVariable {
}
}

/// Used to validate arguments of a function before it is run
public struct FunctionSignature {
let inputs: [FunctionArgumentType]
let varArg: FunctionArgumentType?
Expand Down Expand Up @@ -70,9 +73,12 @@ public struct FunctionSignature {
}
}

/// Protocol for JMESPath function expression
public protocol Function {
/// function signature
static var signature: FunctionSignature { get }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable
/// Evaluate function
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable
}

protocol NumberFunction: Function {
Expand All @@ -81,7 +87,7 @@ protocol NumberFunction: Function {

extension NumberFunction {
static var signature: FunctionSignature { .init(inputs: .number) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .number(let number):
return self.evaluate(number)
Expand All @@ -97,7 +103,7 @@ protocol ArrayFunction: Function {

extension ArrayFunction {
static var signature: FunctionSignature { .init(inputs: .array) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .array(let array):
return self.evaluate(array)
Expand Down Expand Up @@ -136,7 +142,7 @@ struct CeilFunction: NumberFunction {

struct ContainsFunction: Function {
static var signature: FunctionSignature { .init(inputs: .union([.array, .string]), .any) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch (args[0], args[1]) {
case (.array(let array), _):
let result = array.firstIndex(of: args[1]) != nil
Expand All @@ -157,7 +163,7 @@ struct ContainsFunction: Function {

struct EndsWithFunction: Function {
static var signature: FunctionSignature { .init(inputs: .string, .string) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch (args[0], args[1]) {
case (.string(let string), .string(let string2)):
return .boolean(string.hasSuffix(string2))
Expand All @@ -175,7 +181,7 @@ struct FloorFunction: NumberFunction {

struct JoinFunction: Function {
static var signature: FunctionSignature { .init(inputs: .string, .typedArray(.string)) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch (args[0], args[1]) {
case (.string(let separator), .array(let array)):
let strings: [String] = array.map {
Expand All @@ -194,7 +200,7 @@ struct JoinFunction: Function {

struct KeysFunction: Function {
static var signature: FunctionSignature { .init(inputs: .object) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .object(let object):
return .array(object.map { .string($0.key) })
Expand All @@ -206,7 +212,7 @@ struct KeysFunction: Function {

struct LengthFunction: Function {
static var signature: FunctionSignature { .init(inputs: .union([.array, .object, .string])) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .array(let array):
return .number(.init(value: array.count))
Expand All @@ -222,7 +228,7 @@ struct LengthFunction: Function {

struct MapFunction: Function {
static var signature: FunctionSignature { .init(inputs: .expRef, .array) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
switch (args[0], args[1]) {
case (.expRef(let ast), .array(let array)):
let results = try array.map { try runtime.interpret($0, ast: ast) }
Expand All @@ -235,7 +241,7 @@ struct MapFunction: Function {

struct MaxFunction: Function {
static var signature: FunctionSignature { .init(inputs: .union([.typedArray(.string), .typedArray(.number)])) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .array(let array):
if array.count == 0 { return .null }
Expand Down Expand Up @@ -272,7 +278,7 @@ struct MaxFunction: Function {

struct MaxByFunction: Function {
static var signature: FunctionSignature { .init(inputs: .array, .expRef) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
switch (args[0], args[1]) {
case (.array(let array), .expRef(let ast)):
if array.count == 0 { return .null }
Expand Down Expand Up @@ -318,7 +324,7 @@ struct MaxByFunction: Function {

struct MinFunction: Function {
static var signature: FunctionSignature { .init(inputs: .union([.typedArray(.string), .typedArray(.number)])) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .array(let array):
if array.count == 0 { return .null }
Expand Down Expand Up @@ -355,7 +361,7 @@ struct MinFunction: Function {

struct MinByFunction: Function {
static var signature: FunctionSignature { .init(inputs: .array, .expRef) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
switch (args[0], args[1]) {
case (.array(let array), .expRef(let ast)):
if array.count == 0 { return .null }
Expand Down Expand Up @@ -401,7 +407,7 @@ struct MinByFunction: Function {

struct MergeFunction: Function {
static var signature: FunctionSignature { .init(inputs: .object, varArg: .object) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .object(var object):
for arg in args.dropFirst() {
Expand All @@ -420,7 +426,7 @@ struct MergeFunction: Function {

struct NotNullFunction: Function {
static var signature: FunctionSignature { .init(inputs: .any, varArg: .any) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
for arg in args {
guard case .null = arg else {
return arg
Expand All @@ -432,7 +438,7 @@ struct NotNullFunction: Function {

struct ReverseFunction: Function {
static var signature: FunctionSignature { .init(inputs: .union([.array, .string])) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .string(let string):
return .string(String(string.reversed()))
Expand All @@ -446,7 +452,7 @@ struct ReverseFunction: Function {

struct SortFunction: Function {
static var signature: FunctionSignature { .init(inputs: .union([.typedArray(.number), .typedArray(.string)])) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .array(let array):
return .array(array.sorted { $0.compare(.lessThan, value: $1) == true })
Expand All @@ -458,7 +464,7 @@ struct SortFunction: Function {

struct SortByFunction: Function {
static var signature: FunctionSignature { .init(inputs: .array, .expRef) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
struct ValueAndSortKey {
let value: JMESVariable
let sortValue: JMESVariable
Expand Down Expand Up @@ -492,7 +498,7 @@ struct SortByFunction: Function {

struct StartsWithFunction: Function {
static var signature: FunctionSignature { .init(inputs: .string, .string) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch (args[0], args[1]) {
case (.string(let string), .string(let string2)):
return .boolean(string.hasPrefix(string2))
Expand All @@ -518,7 +524,7 @@ struct SumFunction: ArrayFunction {

struct ToArrayFunction: Function {
static var signature: FunctionSignature { .init(inputs: .any) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .array(let array):
return .array(array)
Expand All @@ -530,7 +536,7 @@ struct ToArrayFunction: Function {

struct ToNumberFunction: Function {
static var signature: FunctionSignature { .init(inputs: .any) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
switch args[0] {
case .number(let number):
return .number(number)
Expand All @@ -548,7 +554,7 @@ struct ToNumberFunction: Function {

struct ToStringFunction: Function {
static var signature: FunctionSignature { .init(inputs: .any) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
switch args[0] {
case .string(let string):
return .string(string)
Expand All @@ -560,14 +566,14 @@ struct ToStringFunction: Function {

struct TypeFunction: Function {
static var signature: FunctionSignature { .init(inputs: .any) }
static func evaluate(args: [JMESVariable], runtime: Runtime) throws -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
return .string(args[0].getType())
}
}

struct ValuesFunction: Function {
static var signature: FunctionSignature { .init(inputs: .object) }
static func evaluate(args: [JMESVariable], runtime: Runtime) -> JMESVariable {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) -> JMESVariable {
switch args[0] {
case .object(let object):
return .array(object.map { $0.value })
Expand Down
2 changes: 1 addition & 1 deletion Sources/JMESPath/Interpreter.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

extension Runtime {
extension JMESRuntime {
func interpret(_ data: JMESVariable, ast: Ast) throws -> JMESVariable {
switch ast {
case .field(let name):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

public class Runtime {
/// JMESPath runtime
///
/// Holds list of functions available to JMESPath expression
public class JMESRuntime {
public init() {
self.functions = [:]
self.registerBuiltInFunctions()
Expand Down
Loading

0 comments on commit a4ea9ab

Please sign in to comment.