Skip to content

Commit

Permalink
feature/add-custom-validation-error (OxygenFramework#214)
Browse files Browse the repository at this point in the history
* added custom validation error handling
* cleaned up the string interpolation in the server welcome function
  • Loading branch information
ndortega authored and frankier committed Oct 13, 2024
1 parent 2a8cae7 commit 000fa48
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 12 deletions.
5 changes: 3 additions & 2 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ using RelocatableFolders
using DataStructures: CircularDeque
import Base.Threads: lock, nthreads

include("errors.jl"); @reexport using .Errors
include("util.jl"); @reexport using .Util
include("types.jl"); @reexport using .Types
include("constants.jl"); @reexport using .Constants
Expand Down Expand Up @@ -44,10 +45,10 @@ function serverwelcome(external_url::String, docs::Bool, metrics::Bool, parallel
@info "📦 Version 1.5.12 (2024-06-18)"
@info "✅ Started server: $external_url"
if docs
@info "📖 Documentation: $external_url$docspath"
@info "📖 Documentation: $external_url" * docspath
end
if docs && metrics
@info "📊 Metrics: $external_url$docspath/metrics"
@info "📊 Metrics: $external_url" * "$docspath/metrics"
end
if parallel
@info "🚀 Running in parallel mode with $(Threads.nthreads()) threads"
Expand Down
15 changes: 15 additions & 0 deletions src/errors.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Errors
## In this module, we export commonly used exceptions across the package

export ValidationError

# This is used by the Extractors.jl module to signal that a validation error has occurred
struct ValidationError <: Exception
msg::String
end

function Base.showerror(io::IO, e::ValidationError)
print(io, "Validation Error: $(e.msg)")
end

end
5 changes: 3 additions & 2 deletions src/extractors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ using StructTypes

using ..Util: text, json, formdata, parseparam
using ..Reflection: struct_builder, extract_struct_info
using ..Errors: ValidationError
using ..Types

export Extractor, extract, validate, extracttype, isextractor, isreqparam, isbodyparam,
Expand Down Expand Up @@ -85,14 +86,14 @@ function try_validate(param::Param{U}, instance::T) :: T where {T, U <: Extracto
# Case 1: Use global validate function - returns true if one isn't defined for this type
if !validate(instance)
impl = Base.which(validate, (T,))
throw(ArgumentError("Validation failed for $(param.name): $T \n|> $instance \n|> $impl"))
throw(ValidationError("Validation failed for $(param.name): $T \n|> $instance \n|> $impl"))
end

# Case 2: Use custom validate function from an Extractor (if defined)
if param.hasdefault && param.default isa U && !isnothing(param.default.validate)
if !param.default.validate(instance)
impl = Base.which(param.default.validate, (T,))
throw(ArgumentError("Validation failed for $(param.name): $T \n|> $instance \n|> $impl"))
throw(ValidationError("Validation failed for $(param.name): $T \n|> $instance \n|> $impl"))
end
end

Expand Down
16 changes: 12 additions & 4 deletions src/utilities/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ using HTTP
using JSON3
using Dates

using ..Errors: ValidationError

export countargs, recursive_merge, parseparam,
queryparams, redirect, handlerequest,
format_response!, set_content_size!, format_sse_message


### Request helper functions ###

"""
Expand All @@ -19,7 +20,6 @@ function queryparams(req::HTTP.Request) :: Dict
return HTTP.queryparams(uri.query)
end


"""
redirect(path::String; code = 308)
Expand All @@ -29,6 +29,14 @@ function redirect(path::String; code = 307) :: HTTP.Response
return HTTP.Response(code, ["Location" => path])
end

function handle_error(::ValidationError)
return json(("message" => "400: Bad Request"), status = 400)
end

function handle_error(::Any)
return json(("message" => "500: Internal Server Error"), status = 500)
end

function handlerequest(getresponse::Function, catch_errors::Bool; show_errors::Bool = true)
if !catch_errors
return getresponse()
Expand All @@ -37,9 +45,9 @@ function handlerequest(getresponse::Function, catch_errors::Bool; show_errors::B
return getresponse()
catch error
if show_errors && !isa(error, InterruptException)
@error "ERROR: " exception=(error, catch_backtrace())
@error "ERROR: " exception=(error, catch_backtrace())
end
return json(("message" => "The Server encountered a problem"), status = 500)
return handle_error(error)
end
end
end
Expand Down
8 changes: 4 additions & 4 deletions test/extractortests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ end
# Test that negative age trips the global validator
req = HTTP.Request("GET", "/", [], """name=joe&age=-4""")
param = Param(:form, Form{Person}, missing, false)
@test_throws ArgumentError extract(param, LazyRequest(request=req))
@test_throws Oxygen.Core.Errors.ValidationError extract(param, LazyRequest(request=req))


# Test that age < 25 trips the local validator
req = HTTP.Request("GET", "/", [], """name=joe&age=10""")
default_value = Form{Person}(x -> x.age > 25)
param = Param(:form, Form{Person}, default_value, true)
@test_throws ArgumentError extract(param, LazyRequest(request=req))
@test_throws Oxygen.Core.Errors.ValidationError extract(param, LazyRequest(request=req))
end


Expand Down Expand Up @@ -253,7 +253,7 @@ end
@suppress_err begin
# should fail since we are missing query params
r = internalrequest(HTTP.Request("GET", "/headers", ["limit" => "3"], ""))
@test r.status == 500
@test r.status == 400
end

@suppress_err begin
Expand All @@ -265,7 +265,7 @@ end
"value": 12.0
}
"""))
@test r.status == 500
@test r.status == 400
end

r = internalrequest(HTTP.Request("POST", "/json", [], """
Expand Down

0 comments on commit 000fa48

Please sign in to comment.