Skip to content

Commit b055689

Browse files
authored
Create a specific exception type for enforce (#319)
1 parent 9888543 commit b055689

File tree

8 files changed

+105
-36
lines changed

8 files changed

+105
-36
lines changed

GeometryOpsCore/src/types/exceptions.jl

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ This makes it substantially easier to catch specific kinds of errors and show th
88
For example, we can catch `WrongManifoldException` and show a nice error message,
99
and error hinters can be specialized to that as well.
1010
11+
We also have a custom error type for missing keywords in an algorithm,
12+
which could eventually be extended to have typo detection, et cetera.
13+
1114
```@docs; canonical=false
12-
GeometryOpsCore.WrongManifoldException
15+
WrongManifoldException
16+
MissingKeywordInAlgorithmException
17+
```
18+
19+
```@meta
20+
CollapsedDocStrings = true
1321
```
1422
=#
1523

@@ -46,4 +54,60 @@ function Base.showerror(io::IO, e::WrongManifoldException{I,D,A}) where {I,D,A}
4654
print(io, "\n\n")
4755
print(io, e.description)
4856
end
57+
end
58+
59+
60+
"""
61+
MissingKeywordInAlgorithmException{Alg, F} <: Exception
62+
63+
An error type which is thrown when a keyword argument is missing from an algorithm.
64+
65+
The `alg` argument is the algorithm struct, and the `keyword` argument is the keyword
66+
that was missing.
67+
68+
This error message is used in the [`enforce`](@ref GeometryOps.enforce) method.
69+
70+
## Usage
71+
72+
This is of course not how you would actually use this error type, but it is
73+
how you construct and throw it.
74+
75+
```julia
76+
throw(MissingKeywordInAlgorithmException(GEOS(; tokl = 1.0), my_function, :tol))
77+
```
78+
79+
Real world usage will often look like this:
80+
81+
```julia
82+
function my_function(alg::CLibraryPlanarAlgorithm, args...)
83+
mykwarg = enforce(alg, :mykwarg, my_function) # this will throw an error if :mykwarg is not present in alg
84+
end
85+
```
86+
"""
87+
struct MissingKeywordInAlgorithmException{Alg, F} <: Base.Exception
88+
alg::Alg
89+
f::F
90+
keyword::Symbol
91+
end
92+
93+
_name_of(x::Any) = nameof(typeof(x))
94+
_name_of(x::Function) = nameof(x)
95+
_name_of(x::Type) = nameof(x)
96+
_name_of(s::String) = s
97+
98+
# This is just the generic dispatch, different algorithms can choose to dispatch
99+
# on their own types to provide more specific or interesting error messages.
100+
function Base.showerror(io::IO, e::MissingKeywordInAlgorithmException)
101+
algorithm_name = _name_of(typeof(e.alg))
102+
function_name = _name_of(e.f)
103+
print(io, "The ")
104+
printstyled(io, e.keyword; color = :red)
105+
print(io, " parameter is required for the ")
106+
printstyled(io, algorithm_name; bold = true)
107+
println(io, " algorithm in `$(function_name)`,")
108+
println(io, "but it was not provided.")
109+
println(io)
110+
println(io, "Provide it to the algorithm at construction time, like so:")
111+
println(io, "`$(algorithm_name)(; $(e.keyword) = ...)`")
112+
println(io, "and pass that as the algorithm to `$(function_name)`, usually the first argument.")
49113
end

ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module GeometryOpsLibGEOSExt
77
import GeometryOps as GO, LibGEOS as LG
88
import GeoInterface as GI
99

10-
import GeometryOps: GEOS, enforce, True, False, booltype
10+
import GeometryOps: GEOS, enforce, True, False, booltype, booltype, WithTrait, TraitTarget
1111

1212
using GeometryOps
1313
# The filter statement is required because in Julia, each module has its own versions of these

ext/GeometryOpsLibGEOSExt/simplify.jl

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,18 @@ f
3737
```
3838
3939
=#
40-
GO._simplify(::GI.PointTrait, ::GO.GEOS, geom; kw...) = geom
41-
GO._simplify(::GI.MultiPointTrait, ::GO.GEOS, geom; kw...) = geom
4240

43-
function GO._simplify(::GI.AbstractGeometryTrait, alg::GO.GEOS, geom; kwargs...)
41+
function GO.simplify(alg::GO.GEOS, data; kwargs...)
4442
method = get(alg, :method, :TopologyPreserve)
45-
@assert haskey(alg.params, :tol) """
46-
The `:tol` parameter is required for the GEOS algorithm in `simplify`,
47-
but it was not provided.
48-
49-
Provide it by passing `GEOS(; tol = ...,) as the algorithm.
50-
"""
51-
tol = alg.params.tol
52-
if method == :TopologyPreserve
53-
return LG.topologyPreserveSimplify(GI.convert(LG, geom), tol)
43+
tol = enforce(alg, :tol, GO.simplify)
44+
45+
functor = if method == :TopologyPreserve
46+
(trait, geom) -> LG.topologyPreserveSimplify(GI.convert(LG.geointerface_geomtype(trait), trait, geom), tol)
5447
elseif method == :DouglasPeucker
55-
return LG.simplify(GI.convert(LG, geom), tol)
48+
(trait, geom) -> LG.simplify(GI.convert(LG.geointerface_geomtype(trait), trait, geom), tol)
5649
else
57-
error("Invalid method passed to `GO.simplify(GEOS(...), ...)`: $method. Please use :TopologyPreserve or :DouglasPeucker")
50+
error("Invalid method passed to `GO.simplify(GEOS(...), ...)`: $method. \nPlease use :TopologyPreserve or :DouglasPeucker")
5851
end
59-
end
6052

61-
function GO._simplify(trait::GI.AbstractCurveTrait, alg::GO.GEOS, geom; kw...)
62-
Base.invoke(
63-
GO._simplify,
64-
Tuple{GI.AbstractGeometryTrait, GO.GEOS, typeof(geom)},
65-
trait, alg, geom;
66-
kw...
67-
)
53+
return GO.apply(WithTrait(functor), GO.TraitTarget{GI.AbstractGeometryTrait}(), data; kwargs...)
6854
end
69-

src/transformations/reproject.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export reproject
1717
1818
Reproject any GeoInterface.jl compatible `geometry` from `source_crs` to `target_crs`.
1919
20-
The returned object will be constructed from `GeoInterface.WrapperGeometry`
21-
geometries, wrapping views of a `Vector{Proj.Point{D}}`, where `D` is the dimension.
20+
The returned object will be constructed from GeoInterface wrapper geometries,
21+
wrapping `Vector{NTuple{D, Float64}}`, where `D` is the dimension.
2222
2323
!!! tip
2424
The `Proj.jl` package must be loaded for this method to work,

src/transformations/simplify.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ simplify(
173173
#= For each algorithm, apply simplification to all curves, multipoints, and
174174
points, reconstructing everything else around them. =#
175175
function _simplify(alg::Union{SimplifyAlg, GEOS}, data; prefilter_alg=nothing, kw...)
176-
simplifier(geom) = _simplify(GI.trait(geom), alg, geom; prefilter_alg)
177-
return apply(simplifier, _SIMPLIFY_TARGET, data; kw...)
176+
simplifier(trait, geom) = _simplify(trait, alg, geom; prefilter_alg)
177+
return apply(WithTrait(simplifier), _SIMPLIFY_TARGET, data; kw...)
178178
end
179179

180180

src/types.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function enforce(alg::CLibraryPlanarAlgorithm, kw::Symbol, f)
9595
if haskey(alg.params, kw)
9696
return alg.params[kw]
9797
else
98-
error("$(f) requires a `$(kw)` keyword argument to the `GEOS` algorithm, which was not provided.")
98+
throw(GeometryOpsCore.MissingKeywordInAlgorithmException(alg, f, kw))
9999
end
100100
end
101101

@@ -180,7 +180,7 @@ function enforce(alg::PROJ, kw::Symbol, f)
180180
if haskey(alg.params, kw)
181181
return alg.params[kw]
182182
else
183-
error("$(f) requires a `$(kw)` keyword argument to the `GEOS` algorithm, which was not provided.")
183+
throw(GeometryOpsCore.MissingKeywordInAlgorithmException(alg, f, kw))
184184
end
185185
end
186186

test/core/exceptions.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Test
2+
import GeometryOps as GO
3+
using GeometryOpsCore
4+
using GeometryOpsCore: MissingKeywordInAlgorithmException, WrongManifoldException
5+
6+
# These tests are more testing the call sites than they are testing the exceptions themselves.
7+
# But this is also important to make sure that the whole pipeline works.
8+
@testset "WrongManifoldException" begin
9+
@test_throws WrongManifoldException SingleManifoldAlgorithm{Planar}(Spherical())
10+
@test_throws "Planar" SingleManifoldAlgorithm{Planar}(Spherical())
11+
@test_throws "called with manifold Spherical" SingleManifoldAlgorithm{Planar}(Spherical())
12+
end
13+
14+
@testset "MissingKeywordInAlgorithmException" begin
15+
alg = GO.GEOS(; tol = 1.0)
16+
@test_nowarn GO.enforce(alg, :tol, sum)
17+
@test_throws MissingKeywordInAlgorithmException GO.enforce(alg, :stat, sum)
18+
@test_throws "sum" GO.enforce(alg, :stat, sum)
19+
@test_throws "`stat`" GO.enforce(alg, :stat, sum)
20+
end

test/types.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ using GeometryOps: CLibraryPlanarAlgorithm, GEOS, TG, PROJ
4242
@test Base.get(alg, :c, 0) == 0
4343

4444
# Test enforce method
45-
@test_throws ErrorException enforce(alg, :a, "test")
46-
@test_throws ErrorException enforce(alg, :c, "test")
45+
@test_throws GeometryOpsCore.MissingKeywordInAlgorithmException enforce(alg, :a, "test")
46+
@test_throws GeometryOpsCore.MissingKeywordInAlgorithmException enforce(alg, :c, "test")
4747
end
4848

4949
@testset "GEOS" begin
@@ -76,7 +76,7 @@ end
7676

7777
# Test enforce method
7878
@test enforce(alg, :a, "test") == 1
79-
@test_throws ErrorException enforce(alg, :c, "test")
79+
@test_throws GeometryOpsCore.MissingKeywordInAlgorithmException enforce(alg, :c, "test")
8080
end
8181

8282
@testset "TG" begin
@@ -109,7 +109,7 @@ end
109109

110110
# Test enforce method
111111
@test enforce(alg, :a, "test") == 1
112-
@test_throws ErrorException enforce(alg, :c, "test")
112+
@test_throws GeometryOpsCore.MissingKeywordInAlgorithmException enforce(alg, :c, "test")
113113
end
114114

115115
@testset "PROJ" begin
@@ -144,5 +144,5 @@ end
144144

145145
# Test enforce method
146146
@test enforce(alg1, :a, "test") == 1
147-
@test_throws ErrorException enforce(alg1, :c, "test")
147+
@test_throws GeometryOpsCore.MissingKeywordInAlgorithmException enforce(alg1, :c, "test")
148148
end

0 commit comments

Comments
 (0)