diff --git a/src/Mux.jl b/src/Mux.jl index 44f4ed8..783f918 100644 --- a/src/Mux.jl +++ b/src/Mux.jl @@ -23,6 +23,7 @@ using Hiccup include("lazy.jl") include("server.jl") +include("backtrace_rewriting.jl") include("basics.jl") include("routing.jl") diff --git a/src/backtrace_rewriting.jl b/src/backtrace_rewriting.jl new file mode 100644 index 0000000..644cbff --- /dev/null +++ b/src/backtrace_rewriting.jl @@ -0,0 +1,79 @@ +""" + mux_showerror(io, exc, bt) + +`showerror(io, exc, bt)`, but simplify the printing of all those Mux closures. +""" +function mux_showerror(io, e, bt) + buf = IOBuffer() + showerror(buf, e, bt) + str = String(take!(buf)) + write(io, rename_mux_closures(str)) +end + +""" + find_matching_index(str, idx, closing_char) + +Find the index in `str` of the matching `closing_char` for the opening character at `idx`, or `nothing` if there is no matching character. + +If there is a matching character, `str[idx:find_matching_index(str, idx, closing_char)]` will contain: + +- n opening characters, where 1 ≤ n +- m closing characters, where 1 ≤ m ≤ n + +The interior opening and closing characters need not be balanced. + +# Examples + +``` +julia> find_closing_char("((()))", 1, ')') +6 + +julia> find_closing_char("Vector{Union{Int64, Float64}}()", 7, '}') +29 +``` +""" +function find_closing_char(str, idx, closing_char) + opening_char = str[idx] + open = 1 + while open != 0 && idx < lastindex(str) + idx = nextind(str, idx) + char = str[idx] + if char == opening_char + open += 1 + elseif char == closing_char + open -= 1 + end + end + return open == 0 ? idx : nothing +end + +""" + rename_mux_closures(str) + +Replace all anonymous "Mux.var" closures in `str` with "Mux.Closure" to make backtraces easier to read. +""" +function rename_mux_closures(str) + maybe_idx = findfirst(r"Mux\.var\"#\w+#\w+\"{", str) + if isnothing(maybe_idx) + return str + else + start_idx, brace_idx = extrema(maybe_idx) + end + maybe_idx = find_closing_char(str, brace_idx, '}') + if !isnothing(maybe_idx) + suffix = maybe_idx == lastindex(str) ? "" : str[nextind(str, maybe_idx):end] + str = str[1:prevind(str, start_idx)] * "Mux.Closure" * suffix + rename_mux_closures(str) + else + str + end +end + +""" + Closure + +Mux doesn't really use this type, we just print `Mux.Closure` instead of `Mux.var"#1#2{Mux.var"#3#4"{...}}` in stacktraces to make them easier to read. +""" +struct Closure + Closure() = error("""Mux doesn't really use this type, we just print `Mux.Closure` instead of `Mux.var"#1#2{Mux.var"#3#4"{...}}` in stacktraces to make them easier to read.""") +end diff --git a/src/basics.jl b/src/basics.jl index e7dc708..e094d05 100644 --- a/src/basics.jl +++ b/src/basics.jl @@ -84,7 +84,7 @@ function basiccatch(app, req) println(io, "
$(error_phrases[rand(1:length(error_phrases))])
") println(io, "") - showerror(io, e, catch_backtrace()) + mux_showerror(io, e, catch_backtrace()) println(io, "") return d(:status => 500, :body => codeunits(String(take!(io)))) end @@ -98,3 +98,12 @@ function stderrcatch(app, req) return d(:status => 500, :body => codeunits("Internal server error")) end end + +function prettierstderrcatch(app, req) + try + app(req) + catch e + mux_showerror(stderr, e, catch_backtrace()) + return d(:status => 500, :body => codeunits("Internal server error")) + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 8bfe041..b44d933 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -177,4 +177,22 @@ println("SSL/TLS") end end +@testset "rename_mux_closures" begin + s1 = """ + [4] prettystderrcatch(app::Mux.var"#1#2"{typeof(test), Mux.var"#1#2"{Mux.var"#5#6"{Mux.var"#33#34"{Vector{Any}}, Mux.var"#23#24"{String}}, Mux.var"#1#2"{Mux.var"#5#6"{Mux.var"#33#34"{Vector{SubString{String}}}, Mux.var"#1#2"{Mux.var"#5#6"{Mux.var"#37#38"{Float64}, Mux.var"#23#24"{String}}, Mux.var"#23#24"{String}}}, Mux.var"#1#2"{Mux.var"#5#6"{Mux.var"#33#34"{Vector{SubString{String}}}, var"#5#7"}, Mux.var"#1#2"{Mux.var"#21#22"{Mux.var"#25#26"{Symbol, Int64}}, Mux.var"#23#24"{String}}}}}}, req::Dict{Any, Any})""" + e1 = "[4] prettystderrcatch(app::Mux.Closure, req::Dict{Any, Any})" + @test Mux.rename_mux_closures(s1) == e1 + + s2 = """[21] (::Mux.var"#7#8"{Mux.App})(req::HTTP.Messages.Request)""" + e2 = """[21] (::Mux.Closure)(req::HTTP.Messages.Request)""" + @test Mux.rename_mux_closures(s2) == e2 + + # Replace multiple closures at once + @test Mux.rename_mux_closures(s1 * '\n' * s2) == e1 * '\n' * e2 + + # Leave malformed input alone + s3 = """gabble (::Mux.var"#7#8{ gabble""" + @test Mux.rename_mux_closures(s3) == s3 +end + end