Skip to content

Commit

Permalink
feat(Service): add info function reporting on versions, etc.
Browse files Browse the repository at this point in the history
The Kroki service is build upon a collection of diagramming tools. This
new function is included to make it easier to debug which version of
Kroki, and subsequently which versions of the included diagramming
tools, this package is using at any given time.
  • Loading branch information
bauglir committed Jul 25, 2022
1 parent cec5b10 commit 0d362b5
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 5 deletions.
3 changes: 3 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"

[compat]
Expand All @@ -19,5 +21,6 @@ DocStringExtensions = "0.8, 0.9"
# The wider compatibility range for `HTTP` ensures compatibility with
# up-to-date versions of `Pluto`, etc.
HTTP = "0.8, 0.9, 1"
JSON = "0.21.3"
Reexport = "1.2.2"
julia = "1.6"
2 changes: 1 addition & 1 deletion docs/Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.3"

[[deps.Kroki]]
deps = ["Base64", "CodecZlib", "DocStringExtensions", "HTTP", "Reexport"]
deps = ["Base64", "CodecZlib", "DocStringExtensions", "HTTP", "JSON", "Markdown", "Reexport"]
path = ".."
uuid = "b3565e16-c1f2-4fe9-b4ab-221c88942068"

Expand Down
6 changes: 3 additions & 3 deletions src/Kroki.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ include("./kroki/documentation.jl")
using .Documentation
@setupDocstringMarkup()

include("./kroki/service.jl")
using .Service: ENDPOINT

export Diagram, render

# Convenience short-hand to make further type definitions more straightforward
Expand Down Expand Up @@ -429,6 +426,9 @@ function Base.show(io::IO, diagram::Diagram)
end
end

include("./kroki/service.jl")
using .Service: ENDPOINT

include("./kroki/string_literals.jl")
@reexport using .StringLiterals

Expand Down
20 changes: 20 additions & 0 deletions src/kroki/exceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ function Base.showerror(io::IO, error::DiagramPathOrSpecificationError)
print(io, message)
end

"""
An `Exception` to be thrown when [`Kroki.Service.info`](@ref) cannot retrieve
its information from the Kroki service configured through
[`Kroki.Service.setEndpoint!`](@ref).
"""
struct InfoRetrievalError <: Exception
"The endpoint that was queried for information about a Kroki service."
endpoint::String
end

Base.showerror(io::IO, error::InfoRetrievalError) = print(
io,
"""
The Kroki service at $(error.endpoint) could not be queried for information.
Please verify a Kroki service is active at this address, or reconfigure using
`Kroki.Service.setEndpoint!`.
""",
)

"""
An `Exception` to be thrown when a [`Diagram`](@ref) representing an invalid
specification is passed to [`render`](@ref Kroki.render).
Expand Down
63 changes: 63 additions & 0 deletions src/kroki/service.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Compose](https://docs.docker.com/compose/) are available on the system.
"""
module Service

using HTTP: get as httpget
using JSON: parse as parseJSON
using Markdown: parse as parseMarkdown

using ..Exceptions: InfoRetrievalError
using ..Kroki: toMarkdownLink

using ..Documentation
@setupDocstringMarkup()

Expand Down Expand Up @@ -84,6 +91,62 @@ executeDockerCompose(cmd::String) = executeDockerCompose([cmd])
# be mocked out in tests
const EXECUTE_DOCKER_COMPOSE = Ref{Any}(executeDockerCompose)

function infoVersionOverview(
kroki_service_version::Dict{String, Any},
diagram_type_versions::Vector{String},
)
return parseMarkdown(
"""
The active Kroki service ($(ENDPOINT[])) runs
v$(VersionNumber(kroki_service_version["number"]))
($(kroki_service_version["build_hash"])), which is configured with the
following diagram type versions.
| Diagram Type | Version |
| :-- | :-- |
$(join(diagram_type_versions, '\n'))
!!! info "Diagram type availability"
The presence of a diagram type in this list does not mean it is actually
supported by the service at $(ENDPOINT[]). This is due to some diagram
types requiring additional services that may not be available, as they need
to be managed separately. See [the architecture section on Kroki's
website](https://docs.kroki.io/kroki/architecture) for more information.
""",
)
end

"""
Provides an overview of the (versions of) tools supporting the different
diagram types based on information provided by the service as configured
through [`setEndpoint!`](@ref).
# Example
`julia> Kroki.Service.info()`
$(info())
"""
function info()
try
response = httpget("$(ENDPOINT[])/health")

versions = get(parseJSON(String(response.body)), "version", nothing)

kroki_service_version = get(versions, "kroki", nothing)
delete!(versions, "kroki")

diagram_type_versions = sort([
"| $(toMarkdownLink(Symbol(diagram_type))) | $(version) |" for
(diagram_type, version) in versions
])

return infoVersionOverview(kroki_service_version, diagram_type_versions)
catch
throw(InfoRetrievalError(ENDPOINT[]))
end
end

"""
Sets the [`ENDPOINT`](@ref) using a fallback mechanism if no `endpoint` is
provided.
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[deps]
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
SimpleMock = "a896ed2c-15a5-4479-b61d-a0e88e2a1d25"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
14 changes: 14 additions & 0 deletions test/kroki/exceptions_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ using Test: @test, @test_throws, @testset
using Kroki: Diagram, render
using Kroki.Exceptions:
DiagramPathOrSpecificationError,
InfoRetrievalError,
InvalidDiagramSpecificationError,
InvalidOutputFormatError,
StatusError, # Imported from HTTP through Kroki
Expand Down Expand Up @@ -63,6 +64,19 @@ end
end
end

@testset "`InfoRetrievalError`" begin
expected_endpoint = "http://test.endpoint"

rendered_error = sprint(showerror, InfoRetrievalError(expected_endpoint))

# Should state the cause of the error in a readable fashion
@test occursin("queried for information", rendered_error)

# Should contain the configured `ENDPOINT` and suggest to update it
@test occursin(expected_endpoint, rendered_error)
@test occursin("setEndpoint!", rendered_error)
end

@testset "`RenderError`" begin
# `RenderError`s are thrown from the `render` function whenever an error
# occurs. Some of these benefit from rewrites into more descriptive
Expand Down
49 changes: 48 additions & 1 deletion test/kroki/service_test.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
module ServiceTest

using Kroki.Exceptions: InfoRetrievalError
using Kroki.Service:
DEFAULT_ENDPOINT,
DockerComposeExecutionError,
ENDPOINT,
EXECUTE_DOCKER_COMPOSE,
executeDockerCompose,
info,
setEndpoint!,
start!,
status,
stop!,
update!
using Markdown
using SimpleMock
using Test: @testset, @test, @test_logs, @test_skip
using Test: @test, @test_logs, @test_skip, @test_throws, @testset

# Helper function to temporarily replace `EXECUTE_DOCKER_COMPOSE` with a
# `Mock`. Used to gain control over `docker-compose` behavior for local service
Expand Down Expand Up @@ -73,6 +76,50 @@ end
ENDPOINT[] = original_endpoint
end

@testset "`info`" begin
# These are mostly a collection of spot checks, to make sure the report
# contains at least some useful information
@testset "with a valid Kroki service" begin
info_report = info()

# For the best visualization in different contexts, the report should be
# created as Markdown
@test info_report isa Markdown.MD

# All other tests are more easily performed against the rendered Markdown
rendered_info_report = "$(info_report)"

# The report should include the address of the active Kroki service
@test occursin("active Kroki service ($(ENDPOINT[]))", rendered_info_report)

# The report should contain an overview of diagram types with versions,
# with proper headers
@test occursin(r"| Diagram Type\s+| Version\s+|", rendered_info_report)

# The report should include the friendly names of diagram types and links
# to their websites
@test occursin("[PlantUML](https://plantuml.com)", rendered_info_report)
@test occursin("[Structurizr](https://structurizr.com)", rendered_info_report)

# Kroki itself should not show up within the diagram types table
@test !occursin("[kroki]", rendered_info_report)

# It should include a note no
@test occursin("!!! info \"Diagram type availability\"", rendered_info_report)
end

@testset "without a valid Kroki service" begin
original_endpoint = ENDPOINT[]
setEndpoint!("http://does.not.exist")

try
@test_throws(InfoRetrievalError, info())
finally
setEndpoint!(original_endpoint)
end
end
end

@testset "local instance management" begin
@static if Sys.which("docker-compose") !== nothing
@testset "wraps `docker-compose` with local service definitions" begin
Expand Down

0 comments on commit 0d362b5

Please sign in to comment.