Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-authnz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- chrome
include:
- erlang_version: "27.3"
elixir_version: 1.17.3
elixir_version: 1.18
env:
SELENIUM_DIR: selenium
DOCKER_NETWORK: rabbitmq_net
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test-make-type-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
plugin:
# These are using plugin-specific test jobs.
- rabbit
# - rabbitmq_mqtt # disabled due to Elixir 1.18 JSON conficts
- rabbitmq_mqtt
- rabbitmq_peer_discovery_aws
# These are from the test-plugin test job.
- amqp10_client
Expand Down Expand Up @@ -57,14 +57,14 @@ jobs:
- rabbitmq_shovel
- rabbitmq_shovel_management
- rabbitmq_shovel_prometheus
# - rabbitmq_stomp # disabled due to Elixir 1.18 JSON conficts
# - rabbitmq_stream # disabled due to Elixir 1.18 JSON conficts
- rabbitmq_stomp
- rabbitmq_stream
- rabbitmq_stream_common
- rabbitmq_stream_management
- rabbitmq_tracing
- rabbitmq_trust_store
- rabbitmq_web_dispatch
# - rabbitmq_web_mqtt # disabled due to Elixir 1.18 JSON conficts
- rabbitmq_web_mqtt
- rabbitmq_web_stomp
# This one we do not want to run tests so no corresponding test job.
- rabbitmq_ct_helpers
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test-make.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- '27'
- '28'
elixir_version:
- '1.18'
- '1.19'
# @todo Add macOS and Windows.
runs-on: ubuntu-latest
timeout-minutes: 60
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
erlang_version:
- '28'
elixir_version:
- '1.18'
- '1.19'
uses: ./.github/workflows/test-make-tests.yaml
with:
erlang_version: ${{ matrix.erlang_version }}
Expand All @@ -79,7 +79,7 @@ jobs:
erlang_version:
- '28'
elixir_version:
- '1.18'
- '1.19'
uses: ./.github/workflows/test-make-tests.yaml
with:
erlang_version: ${{ matrix.erlang_version }}
Expand All @@ -94,7 +94,7 @@ jobs:
erlang_version: # Latest OTP
- '28'
elixir_version: # Latest Elixir
- '1.18'
- '1.19'
uses: ./.github/workflows/test-make-type-check.yaml
with:
erlang_version: ${{ matrix.erlang_version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-management-ui-for-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- chrome
include:
- erlang_version: "27.3"
elixir_version: 1.17
elixir_version: 1.18
env:
SELENIUM_DIR: selenium
DOCKER_NETWORK: rabbitmq_net
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-management-ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- chrome
include:
- erlang_version: "27.3"
elixir_version: 1.17.3
elixir_version: 1.18
env:
SELENIUM_DIR: selenium
DOCKER_NETWORK: rabbitmq_net
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-upgrades.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
uses: erlef/setup-beam@v1
with: # Versions repeated later in this file.
otp-version: '27'
elixir-version: '1.18'
elixir-version: '1.19'
hexpm-mirrors: |
https://builds.hex.pm
https://cdn.jsdelivr.net/hex
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
uses: erlef/setup-beam@v1
with:
otp-version: '27'
elixir-version: '1.18'
elixir-version: '1.19'
hexpm-mirrors: |
https://builds.hex.pm
https://cdn.jsdelivr.net/hex
Expand Down
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ XREF_SCOPE = app deps
# protocols directly.
XREF_IGNORE = [ \
{'Elixir.CSV.Encode',impl_for,1}, \
{'Elixir.JSON.Decoder',impl_for,1}, \
{'Elixir.JSON.Encoder',impl_for,1}, \
{'Elixir.RabbitMQ.CLI.Core.DataCoercion',impl_for,1}]

# Include Elixir libraries in the Xref checks.
Expand Down
9 changes: 7 additions & 2 deletions deps/rabbitmq_cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ define PROJECT_ENV
endef

BUILD_DEPS = rabbit_common
DEPS = csv json stdout_formatter
DEPS = csv stdout_formatter
LOCAL_DEPS = elixir

TEST_DEPS = amqp amqp_client temp x509 rabbit

dep_amqp = hex 3.3.0
dep_csv = hex 3.2.1
dep_json = hex 1.4.1
dep_temp = hex 0.4.9
dep_x509 = hex 0.9.2

Expand Down Expand Up @@ -141,6 +140,12 @@ endif
dialyzer:: escript
MIX_ENV=test mix dialyzer

# rabbitmq_cli is a pure Elixir project, so erlang.mk's `dialyze` target
# has no .erl files to analyze. Override it as a successful no-op; use
# `gmake dialyzer` (above) for an Elixir Dialyzer run via Mix.
dialyze:
@:

.PHONY: install

install: $(ESCRIPT_FILE)
Expand Down
131 changes: 131 additions & 0 deletions deps/rabbitmq_cli/lib/rabbitmq/cli/core/json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
## This Source Code Form is subject to the terms of the Mozilla Public
## License, v. 2.0. If a copy of the MPL was not distributed with this
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
##
## Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.

defmodule RabbitMQ.CLI.Core.JSON do
@moduledoc """
Thin JSON facade used by the CLI tools.

Wraps `:thoas` so the rest of the codebase does not depend on a specific
backend, and so the module name does not collide with the `JSON` module
added to Elixir's standard library in 1.18.

`encode/1` returns `{:ok, binary}` and `decode/1` returns
`{:ok, term} | {:error, term}`, matching the shape that callers throughout
`rabbitmq_cli` already rely on.
"""

@spec encode(term()) :: {:ok, binary()}
def encode(term) do
{:ok, :thoas.encode(normalize(term))}
end

@spec decode(iodata()) :: {:ok, term()} | {:error, term()}
def decode(bin) do
:thoas.decode(bin)
end

# Convert Erlang strings (lists of integers) to binaries for proper JSON
# encoding and convert other Erlang-specific terms to readable strings.
defp normalize(data) when is_function(data) do
"Fun()"
end

defp normalize(data) when is_pid(data) do
"Pid(#{inspect(data)})"
end

defp normalize(data) when is_port(data) do
"Port(#{inspect(data)})"
end

defp normalize(data) when is_reference(data) do
"Ref(#{inspect(data)})"
end

defp normalize(data) when is_binary(data) do
convert_binary(data)
end

defp normalize([]), do: []

# Likely a value like [5672], which we don't want to convert to the
# equivalent unicode codepoint.
defp normalize([val] = data) when is_integer(val) and val > 255 do
data
end

# Likely a value like [5672, 5682], which we don't want to convert to
# the equivalent unicode codepoint.
defp normalize([v0, v1] = data)
when is_integer(v0) and v0 > 255 and is_integer(v1) and v1 > 255 do
data
end

defp normalize([b | rest]) when is_binary(b) do
[convert_binary(b) | normalize(rest)]
end

defp normalize(data) when is_list(data) do
if proplist?(data) do
# `:thoas` encodes maps as JSON objects but is unreliable on lists of
# 2-tuples that contain non-proplist values nested inside, so we hand it
# a real map.
Map.new(data, fn {k, v} -> {normalize(k), normalize(v)} end)
else
try do
case :unicode.characters_to_binary(data, :utf8) do
binary when is_binary(binary) ->
binary

_ ->
Enum.map(data, &normalize/1)
end
rescue
ArgumentError ->
Enum.map(data, &normalize/1)
end
end
end

# `:thoas` does not accept bare tuples (only proplist 2-tuples nested in a
# list, handled above). Convert any other tuple to a list so it encodes as a
# JSON array, matching what the previous JSON library used to do.
defp normalize(data) when is_tuple(data) do
data
|> Tuple.to_list()
|> Enum.map(&normalize/1)
end

defp normalize(data) when is_map(data) do
Enum.into(data, %{}, fn {k, v} -> {normalize(k), normalize(v)} end)
end

defp normalize(data), do: data

defp proplist?([_ | _] = list) do
Enum.all?(list, fn
{k, _v} when is_atom(k) or is_binary(k) -> true
_ -> false
end)
end

defp proplist?(_), do: false

defp convert_binary(data) when is_binary(data) do
try do
case :unicode.characters_to_binary(data, :utf8) do
binary when is_binary(binary) ->
binary

_ ->
Base.encode64(data)
end
rescue
ArgumentError ->
Base.encode64(data)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ExportDefinitionsCommand do
defp serialise(raw_map, "json") do
# rabbit_definitions already takes care of transforming all
# proplists into maps
{:ok, json} = JSON.encode(raw_map)
{:ok, json} = RabbitMQ.CLI.Core.JSON.encode(raw_map)
json
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ImportDefinitionsCommand do
#

defp deserialise(bin, "json") do
JSON.decode(bin)
RabbitMQ.CLI.Core.JSON.decode(bin)
end

defp deserialise(bin, "erlang") do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListUserLimitsCommand do

val ->
Enum.map(val, fn {user, val} ->
{:ok, val_encoded} = JSON.encode(Map.new(val))
{:ok, val_encoded} = RabbitMQ.CLI.Core.JSON.encode(Map.new(val))
[user: user, limits: val_encoded]
end)
end
Expand All @@ -56,7 +56,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListUserLimitsCommand do
{:badrpc, node}

val when is_list(val) or is_map(val) ->
{:ok, val_encoded} = JSON.encode(Map.new(val))
{:ok, val_encoded} = RabbitMQ.CLI.Core.JSON.encode(Map.new(val))
val_encoded
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListVhostLimitsCommand do

val ->
Enum.map(val, fn {vhost, val} ->
{:ok, val_encoded} = JSON.encode(Map.new(val))
{:ok, val_encoded} = RabbitMQ.CLI.Core.JSON.encode(Map.new(val))
[vhost: vhost, limits: val_encoded]
end)
end
Expand All @@ -54,7 +54,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.ListVhostLimitsCommand do
{:badrpc, node}

val when is_list(val) or is_map(val) ->
JSON.encode(Map.new(val))
RabbitMQ.CLI.Core.JSON.encode(Map.new(val))
end
end

Expand Down
Loading
Loading