# HG changeset patch # User Theodor Thornhill # Date 1639691178 -3600 # Thu Dec 16 22:46:18 2021 +0100 # Node ID 5a5918998dd6a2957f04299333019d5009b05ec1 # Parent e499aa8028e7c55568a2c599e4e31ef388e40f90 Pass some errors onwards diff --git a/src/execution.lisp b/src/execution.lisp --- a/src/execution.lisp +++ b/src/execution.lisp @@ -229,43 +229,54 @@ ;; TODO: https://spec.graphql.org/draft/#CoerceResult() ;; TODO: #28 (let ((leaf-type-name (if (typep (kind leaf-type) 'wrapper-type) - (name (ty leaf-type)) + (nameof (ty leaf-type)) (nameof leaf-type)))) (etypecase value ;; TODO: This should report a field error if out of coerce range. (integer - (or (and (string= leaf-type-name "Int") (coerce value '(signed-byte 32))) - "Field error for int")) + (if (string= leaf-type-name "Int") + (coerce value '(signed-byte 32)) + (push-error (format nil "Cannot coerce result into Int for value: ~a when value should be: ~a" + value leaf-type-name) + leaf-type))) ;; TODO: This should report a field error if non-finite internal values (NaN ;; and Infinity. ((or single-float double-float) - (or (and (string= leaf-type-name "Float") (coerce value 'double-float)) - "Field error for float")) - ;; TODO: We may return "true" for t and "1" for integer 1. - (string - (or (and (or (string= leaf-type-name "String") - (string= leaf-type-name "ID")) - value) - "Field error for string")) + (if (string= leaf-type-name "Float") + (coerce value 'double-float) + (push-error (format nil "Cannot coerce result into Float for value: ~a when value should be: ~a" + value leaf-type-name) + leaf-type))) (string-value - (or (and (or (string= (name leaf-type-name) "String")) - (value value)) - "Field error for string-value")) + (value value)) (enum-value - (or (and (or (string= (name leaf-type-name) "String") - (string= leaf-type-name "String")) - (value value)) - "Field error for enum-value")) + (if (or (string= (name leaf-type-name) "String") (string= leaf-type-name "String")) + (value value) + (push-error (format nil "Cannot coerce result into Enum value for value: ~a when value should be: ~a" + value leaf-type-name) + leaf-type))) (name ;; TODO: Should this be possible?? - (or (and (string= leaf-type-name "String") - (name value)) - "Field error for name-value")) + (if (string= leaf-type-name "String") + (name value) + (push-error (format nil "Cannot coerce result into String for value: ~a when value should be: ~a" + value leaf-type-name) + leaf-type))) ;; TODO: Add other clauses for other literal values - (bool - (or (and (string= leaf-type-name "Boolean") - (if (equal value 'true) "true" "false")) - "Field error for boolean")) - (t "We really screwed up result coercing here!")))) + (bool ;; TODO: Make sure we get the correct bool/nil/t/true/false + (if (string= leaf-type-name "Boolean") + (if (equal value 'true) "true" "false") + (push-error (format nil "Cannot coerce result into Boolean for value: ~a when type should be: ~a" + value leaf-type-name ) + leaf-type))) + (string + (if (or (string= leaf-type-name "String") (string= leaf-type-name "ID")) + value + (push-error (format nil "Cannot coerce result into String or ID for value: ~a when type should be: ~a" + value leaf-type-name) + leaf-type))) + (t (push-error (format nil "Cannot coerce result for value: ~a when value should be: ~a " + value leaf-type-name) + leaf-type))))) (defun resolve-abstract-type (abstract-type object-value) ;; TODO: https://spec.graphql.org/draft/#ResolveAbstractType() diff --git a/src/utils.lisp b/src/utils.lisp --- a/src/utils.lisp +++ b/src/utils.lisp @@ -52,14 +52,16 @@ column) (defun location-errors (nodes) - (mapcar - (lambda (node) - (with-slots (line column) (start-token (location node)) - (make-instance - 'error-location - :line line - :column column))) - nodes)) + (remove nil + (mapcar + (lambda (node) + (when (location node) + (with-slots (line column) (start-token (location node)) + (make-instance + 'error-location + :line line + :column column)))) + nodes))) (defun push-error (message nodes) (push (make-instance diff --git a/t/execution-tests.lisp b/t/execution-tests.lisp --- a/t/execution-tests.lisp +++ b/t/execution-tests.lisp @@ -13,6 +13,7 @@ (ok (= (hash-table-count result) 2)) (ok (= (length (gethash "a" result)) 2)) (ok (= (length (gethash "b" result)) 1)))))) + (testing "get-operation should return the correct operation" (let ((doc (gql::build-document "{ a { subfield1 } } "))) (ok (gql::get-operation doc "Query"))) @@ -27,6 +28,7 @@ (ok (gql::get-operation doc))) (let ((doc (build-schema "mutation { a { subfield1 } } "))) (ok (gql::get-operation doc)))) + (testing "merge-selection-sets should merge multiple fields" (let* ((definitions (gql::definitions (build-schema (asdf:system-relative-pathname 'gql-tests #p"t/test-files/validation-schema.graphql")))) (query-type (find-if (lambda (x) (string= (gql::nameof x) "Query")) definitions))) @@ -44,6 +46,7 @@ (ok (= (hash-table-count dog-res) 2)) (ok (gethash "name" dog-res)) (ok (gethash "owner" dog-res)))))) + (testing "A query should handle alias" (let* ((definitions (gql::definitions (build-schema (asdf:system-relative-pathname 'gql-tests #p"t/test-files/validation-schema.graphql")))) (query-type (find-if (lambda (x) (string= (gql::nameof x) "Query")) definitions))) @@ -61,51 +64,37 @@ (ok (= (hash-table-count dog-res) 2)) (ok (gethash "name" dog-res)) (ok (gethash "owner" dog-res)))))) - (testing "A query should handle variables and arguments" - (let* ((definitions (gql::definitions (build-schema (asdf:system-relative-pathname 'gql-tests #p"t/test-files/validation-schema.graphql")))) - (query-type (find-if (lambda (x) (string= (gql::nameof x) "Query")) definitions))) - (with-context (:schema (gql::make-schema :query query-type :types definitions) - :document (build-schema "query x($sit: String) { dog { doesKnowCommand(dogCommand: $sit) } }")) - (setf (gethash "sit" (gql::variables gql::*context*)) "SIT") - (gql::set-resolver "Dog" "name" (lambda () "Bingo-bongo")) - (gql::set-resolver "Dog" "doesKnowCommand" - (lambda () - (if (string= (gethash "dogCommand" (gql::arg-values (gql::execution-context gql::*context*) )) "SIT") - 'true 'false))) - (gql::set-resolver "Query" "dog" (lambda () t)) - (let* ((res (gql::execute nil nil)) - (data (gethash "data" res)) - (dog (gethash "dog" data)) - (command (gethash "doesKnowCommand" dog))) - (ok (string= command "true")))))) + (testing "Result coercing" - (flet ((named-type (name) - (make-instance 'gql::named-type - :name (make-instance 'gql::name :name name)))) - (flet ((test (name value ty &optional return-val) - (let ((res (gql::coerce-result (named-type name) value))) - (and (typep res ty) - (equalp res (or return-val value)))))) - (ok (test "Int" 3 'integer)) - (ok (test "Int" -3 'integer)) - (ng (test "String" -3 'integer "Field error for int")) - (ng (test "Boolean" -3 'integer "Field error for int")) + (flet ((test (name value ty &optional return-val) + (let* ((gql::*errors* nil) + (res (gql::coerce-result (named name) value))) + (and (typep res ty) (equalp res (or return-val value))))) + (test-error (name value error-message) + (let* ((gql::*errors* nil) + (res (gql::coerce-result (named name) value))) + (ok (string= error-message (gql::message (car res))))))) + (ok (test "Int" 3 'integer)) + (ok (test "Int" -3 'integer)) + (test-error "String" -3 "Cannot coerce result into Int for value: -3 when value should be: String") + (test-error "Boolean" -3 "Cannot coerce result into Int for value: -3 when value should be: Boolean") - (ok (test "Float" -3.9 'double-float)) - (ok (test "Float" 3.9 'double-float)) - (ok (test "Float" 3342.91231236 'double-float)) - (ng (test "Int" 3342.91231236 'double-float)) - (ng (test "String" 3342.91231236 'double-float)) - (ng (test "Boolean" 3342.91231236 'double-float)) + (ok (test "Float" -3.9 'double-float)) + (ok (test "Float" 3.9 'double-float)) + (ok (test "Float" 3342.91231236 'double-float)) + (ng (test "Int" 3342.91231236 'double-float)) + (ng (test "String" 3342.91231236 'double-float)) + (ng (test "Boolean" 3342.91231236 'double-float)) - (ok (test "String" "Look at this string!" 'string "Look at this string!")) - (ok (test "ID" "Look at this string!" 'string)) - (ok (test "Boolean" "Look at this string!" 'string "Field error for string")) - (ok (test "Int" "Look at this string!" 'string "Field error for string")) - (ok (test "" "Look at this string!" 'string "Field error for string")) + (ok (test "String" "Look at this string!" 'string "Look at this string!")) + (ok (test "ID" "Look at this string!" 'string)) + (test-error "Boolean" "Look at this string!" "Cannot coerce result into String or ID for value: Look at this string! when type should be: Boolean") + (test-error "Int" "Look at this string!" "Cannot coerce result into String or ID for value: Look at this string! when type should be: Int") + (test-error "" "Look at this string!" "Cannot coerce result into String or ID for value: Look at this string! when type should be: ") - (ok (test "Boolean" 'true 'string "true")) - (ok (test "Boolean" 'false 'string "false"))))) + (ok (test "Boolean" 'true 'string "true")) + (ok (test "Boolean" 'false 'string "false")))) + (testing "Using resolvers that access the object from the 'db'" (let* ((definitions (gql::definitions (build-schema (asdf:system-relative-pathname 'gql-tests #p"t/test-files/validation-schema.graphql")))) (query-type (find-if (lambda (x) (string= (gql::nameof x) "Query")) definitions))) @@ -128,6 +117,7 @@ (dog (gethash "dog" data)) (name (gethash "bongo" dog))) (ok (string= name "Bingo-bongo")))))) + (testing "A query should handle variables and arguments" (let* ((definitions (gql::definitions (build-schema (asdf:system-relative-pathname 'gql-tests #p"t/test-files/validation-schema.graphql")))) (query-type (find-if (lambda (x) (string= (gql::nameof x) "Query")) definitions))) @@ -159,16 +149,6 @@ (dog (gethash "dog" data)) (command (gethash "doesKnowCommand" dog))) (ok (string= command "false"))) - ;; (setf (gethash "sit" variable-values) "SIT") - ;; (let* ((res (gql::execute - ;; (build-schema "query { dog { doesKnowCommand(dogCommand: \"SIT\") } }") - ;; nil - ;; variable-values - ;; nil)) - ;; (data (gethash "data" res)) - ;; (dog (gethash "dog" data)) - ;; (command (gethash "doesKnowCommand" dog))) - ;; (ok (string= command "true"))) (setf (gql::document gql::*context*) (build-schema "query { dog { doesKnowCommand(dogCommand: \"LOL\") } }")) (let* ((res (gql::execute nil nil)) (data (gethash "data" res))