diff --git a/Project.toml b/Project.toml index dfc91157..cc6e5add 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Oxygen" uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b" authors = ["Nathan Ortega "] repo = "https://github.com/OxygenFramework/Oxygen.jl.git" -version = "1.5.12" +version = "1.5.13" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/src/core.jl b/src/core.jl index 489814c9..1cc0195d 100644 --- a/src/core.jl +++ b/src/core.jl @@ -10,8 +10,9 @@ using Dates using Reexport using RelocatableFolders using DataStructures: CircularDeque -import Base.Threads: lock +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 @@ -41,16 +42,26 @@ oxygen_title = raw""" function serverwelcome(external_url::String, docs::Bool, metrics::Bool, parallel::Bool, docspath::String) printstyled(oxygen_title, color=:blue, bold=true) - @info "📦 Version 1.5.12 (2024-06-18)" + @info "📦 Version 1.5.13 (2024-09-02)" @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" + # Check if the current julia version supports interactive threadpools + if hasmethod(getfield(Threads, Symbol("@spawn")), Tuple{LineNumberNode, Module, Symbol, Expr}) + # Add a warnign if the interactive threadpool is empty when running in parallel mode + if nthreads(:interactive) == 0 + @warn """ + 🚨 Interactive threadpool is empty. This can hurt performance when running in parallel mode. + Try launching julia like \"julia --threads 3,1\" to add 1 thread to the interactive threadpool. + """ + end + end end end diff --git a/src/errors.jl b/src/errors.jl new file mode 100644 index 00000000..361dcd76 --- /dev/null +++ b/src/errors.jl @@ -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 \ No newline at end of file diff --git a/src/extractors.jl b/src/extractors.jl index 7a70f577..c2bd9929 100644 --- a/src/extractors.jl +++ b/src/extractors.jl @@ -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, @@ -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 diff --git a/src/utilities/misc.jl b/src/utilities/misc.jl index cf56730d..3ccd8ee1 100644 --- a/src/utilities/misc.jl +++ b/src/utilities/misc.jl @@ -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 ### """ @@ -19,7 +20,6 @@ function queryparams(req::HTTP.Request) :: Dict return HTTP.queryparams(uri.query) end - """ redirect(path::String; code = 308) @@ -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() @@ -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 diff --git a/test/extractortests.jl b/test/extractortests.jl index 8e94c7d2..73bcb286 100644 --- a/test/extractortests.jl +++ b/test/extractortests.jl @@ -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 @@ -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 @@ -265,7 +265,7 @@ end "value": 12.0 } """)) - @test r.status == 500 + @test r.status == 400 end r = internalrequest(HTTP.Request("POST", "/json", [], """