Skip to content

Commit

Permalink
Allow an item in one extension to patch an item in another extension (#…
Browse files Browse the repository at this point in the history
…111)

* Fix to allow an item (a class or object) in one extension to patch an item in another extension.

* Update dependencies

* In test schema, add second patch of device to the rpg2 extension.
  • Loading branch information
rmouritzen-splunk authored Sep 20, 2024
1 parent f0de5e9 commit 29c4744
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 35 deletions.
71 changes: 50 additions & 21 deletions lib/schema/cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,9 @@ defmodule Schema.Cache do
|> put_in([:attributes, :category_uid, :_source], name)
end

# Adds :_source key to each attribute of item. This must be done before processing (compiling)
# inheritance and patching (resolve_extends and patch_type) since after that processing the
# original source is lost.
defp attribute_source({item_key, item}) do
item =
Map.update(
Expand All @@ -942,22 +945,19 @@ defmodule Schema.Cache do
{key, nil}

{key, attribute} ->
attribute = Map.put(attribute, :_source, item_key)

attribute =
if patch_extends?(item) do
# TODO: HACK. This is part of the code compensating for the
# Schema.Cache.patch_type processing. An attribute _source for an
# extension class or object that uses this patch mechanism
# keeps the form "<extension>/<name>" form, which doesn't refer to
# anything after the patch_type processing. This requires a deeper change
# to fix, so here we just keep an extra _source_patched key.
Map.put(attribute, :_source_patched, String.to_atom(patch_name(item)))
else
if patch_extends?(item) do
# Because attribute_source done before patching with patch_type, we need to
# capture the final "patched" type for use by the UI when displaying the source.
# Other uses of :_source require the original pre-patched source.
{
key,
attribute
end

{key, attribute}
|> Map.put(:_source, item_key)
|> Map.put(:_source_patched, String.to_atom(item[:extends]))
}
else
{key, Map.put(attribute, :_source, item_key)}
end
end
)
end
Expand All @@ -968,11 +968,24 @@ defmodule Schema.Cache do

defp patch_type(items, kind) do
Enum.reduce(items, %{}, fn {key, item}, acc ->
# Logger.debug(fn ->
# patching? =
# if patch_extends?(item) do
# " PATCHING:"
# else
# "not patching:"
# end

# "#{patching?} key #{inspect(key)}," <>
# " name #{inspect(item[:name])}, extends #{inspect(item[:extends])}," <>
# " caption #{inspect(item[:caption])}, extension #{inspect(item[:extension])}"
# end)

if patch_extends?(item) do
# This is an extension class or object with the same name its own base class
# (The name is not prefixed with the extension name, unlike a key / uid.)

name = patch_name(item)
name = item[:extends]

base_key = String.to_atom(name)

Expand Down Expand Up @@ -1010,14 +1023,30 @@ defmodule Schema.Cache do
end)
end

defp patch_name(item) do
item[:name] || item[:extends]
end

# Is this item a special patch extends definition as done by patch_type.
# It is triggered by a class or object that has no name or the name is the same as the extends.
defp patch_extends?(item) do
patch_name(item) == item[:extends]
extends = item[:extends]

if extends do
name = item[:name]

if name do
if name == extends do
# name and extends are the same
true
else
# true if name matches extends without extension scope ("extension/name" -> "name")
# This when an item in one extension patches an item in another.
name == Utils.descope(extends)
end
else
# extends with no name
true
end
else
false
end
end

defp patch_constraints(base, item) do
Expand Down
18 changes: 7 additions & 11 deletions lib/schema_web/views/page_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ defmodule SchemaWeb.PageView do
end
end

defp get_hierarchy_source(field) do
# In the case of an attribute from a patched class or object, we want to display the final
# compiled type, which is in :_source_patched and in this case :_source contains the name of
# the pre-patched item.
field[:_source_patched] || field[:_source]
end

# Build a class or object hierarchy path from item_key to target_parent_item_key.
# Returns {true, path} when a path is found, and {false, []} otherwise.
@spec build_hierarchy(atom(), atom(), map(), list()) :: {boolean(), list()}
Expand Down Expand Up @@ -261,17 +268,6 @@ defmodule SchemaWeb.PageView do
end
end

defp get_hierarchy_source(field) do
# TODO: HACK. This is part of the code compensating for the
# Schema.Cache.patch_type processing. An attribute _source for an
# extension class or object that uses this patch mechanism
# keeps the form "<extension>/<name>" form, which doesn't refer to
# anything after the patch_type processing. This requires a deeper change
# to fix, so here we just keep an extra _source_patched key.
# Use "fixed" :_source_patched if available
field[:_source_patched] || field[:_source]
end

defp format_hierarchy(path, all_items, kind) do
Enum.map(
path,
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
defmodule Schema.MixProject do
use Mix.Project

@version "2.73.0"
@version "2.73.1"

def project do
build = System.get_env("GITHUB_RUN_NUMBER") || "SNAPSHOT"
Expand Down
4 changes: 2 additions & 2 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
}
7 changes: 7 additions & 0 deletions test/test_ocsf_schema/extensions2/rpg2/extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"caption": "RPG 2",
"description": "The second RPG extension extends the RPG extension.",
"name": "rpg2",
"uid": 43,
"version": "0.1.0-rpg2-test"
}
10 changes: 10 additions & 0 deletions test/test_ocsf_schema/extensions2/rpg2/objects/device_(patch).json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"caption": "RPG 2 Device",
"description": "Patch extends of Device for the RPG 2 extension.",
"extends": "device",
"attributes": {
"rpg/hp": {
"requirement": "optional"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"caption": "RPG 2 Hammer Patch",
"extends": "rpg/hammer",
"attributes": {
"rpg/hp": {
"requirement": "optional"
}
}
}
20 changes: 20 additions & 0 deletions test/test_ocsf_schema/extensions2/rpg2/objects/sword.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"caption": "Sword",
"description": "The Sword object represents a stabby instrument of death.",
"name": "sword",
"attributes": {
"desc": {
"caption": "Description",
"description": "The description of the sword, ordinarily as reported by the blacksmith.",
"requirement": "optional"
},
"name": {
"description": "The sword's name. Named swords are just cooler.",
"requirement": "recommended"
},
"rpg/damage_range": {
"description": "The amount of damage that could be inflicted by this sword.",
"requirement": "recommended"
}
}
}

0 comments on commit 29c4744

Please sign in to comment.