Skip to content
19 changes: 12 additions & 7 deletions lib/astarte/core/generators/interface.ex
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ defmodule Astarte.Core.Generators.Interface do
@spec major_version :: StreamData.t(integer())
def major_version, do: integer(0..9)

@doc false
@spec minor_version(major_version :: integer()) :: StreamData.t(integer())
def minor_version(0), do: integer(1..255)
def minor_version(_n), do: integer(0..255)

defp name_optional do
gen all first <- string([?a..?z, ?A..?Z], length: 1),
rest <- string(:alphanumeric, max_length: 10),
Expand Down Expand Up @@ -174,13 +179,6 @@ defmodule Astarte.Core.Generators.Interface do
defp id(interface_name, major_version),
do: constant(CQLUtils.interface_id(interface_name, major_version))

defp minor_version(major_version) do
case major_version do
0 -> integer(1..255)
_n -> integer(0..255)
end
end

defp ownership, do: member_of([:device, :server])

defp interface_mapping(params, endpoint),
Expand Down Expand Up @@ -237,6 +235,13 @@ defmodule Astarte.Core.Generators.Interface do

# Utilities

@doc false
@spec endpoint_by_aggregation(:individual | :object, String.t()) :: String.t()
def endpoint_by_aggregation(:individual, endpoint), do: endpoint

def endpoint_by_aggregation(:object, endpoint),
do: endpoint |> String.split("/") |> Enum.drop(-1) |> Enum.join("/")

@normalized_param ""

@doc false
Expand Down
82 changes: 42 additions & 40 deletions lib/astarte/core/generators/mapping/value.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule Astarte.Core.Generators.Mapping.Value do
interface
end

gen_interface_base |> bind(&build_package/1)
gen_interface_base |> bind(fn interface -> build_package(interface, params) end)
end

@doc """
Expand All @@ -68,68 +68,53 @@ defmodule Astarte.Core.Generators.Mapping.Value do
|> fixed_map())
)

defp build_package(%Interface{aggregation: :individual} = interface) do
defp build_package(%Interface{aggregation: :individual = aggregation} = interface, params) do
%Interface{mappings: mappings} = interface

gen all %Mapping{endpoint: endpoint, value_type: value_type} <- member_of(mappings),
path <- endpoint_path(endpoint),
value <- build_value(value_type) do
%{path: path, value: value, type: value_type}
params gen all %Mapping{endpoint: endpoint, value_type: value_type} <- member_of(mappings),
type <- constant(value_type),
endpoint = InterfaceGenerator.endpoint_by_aggregation(aggregation, endpoint),
path <- path_from_endpoint(endpoint),
value <- build_value(type),
params: params do
%{path: path, value: value, type: type}
end
end

defp build_package(%Interface{aggregation: :object} = interface) do
defp build_package(%Interface{aggregation: :object = aggregation} = interface, params) do
%Interface{mappings: [%Mapping{endpoint: endpoint} | _] = mappings} = interface

endpoint = endpoint |> String.split("/") |> Enum.drop(-1) |> Enum.join("/")

gen all path <- endpoint_path(endpoint),
type <-
mappings
|> Map.new(fn %Mapping{endpoint: endpoint, value_type: value_type} ->
{endpoint_postfix(endpoint), value_type}
end)
|> optional_map(),
value <- object_value_from_type(type) do
endpoint = InterfaceGenerator.endpoint_by_aggregation(aggregation, endpoint)

params gen all path <- path_from_endpoint(endpoint),
type <-
mappings
|> Map.new(fn %Mapping{endpoint: endpoint, value_type: value_type} ->
{endpoint_postfix(endpoint), value_type}
end)
|> optional_map(),
value <- object_value_from_type(type),
params: params do
%{path: path, value: value, type: type}
end
end

defp endpoint_path(endpoint) do
endpoint
|> String.split("/")
|> Enum.map(&convert_token/1)
|> fixed_list()
|> map(&Enum.join(&1, "/"))
end

defp endpoint_postfix(endpoint), do: Regex.replace(~r/.*\//, endpoint, "")

defp build_value(value_type), do: ValueTypeGenerator.value_from_type(value_type)

# Utilities
defp convert_token(token) do
case(Mapping.is_placeholder?(token)) do
true -> string(:alphanumeric, min_length: 1)
false -> constant(token)
end
end

@doc """
Returns true if `path` matches `endpoint` according to the given `aggregation`.
"""
@spec path_matches_endpoint?(:individual | :object, String.t(), String.t()) :: boolean()
def path_matches_endpoint?(:individual, endpoint, path),
do:
path_matches_endpoint?(
endpoint |> Mapping.normalize_endpoint() |> String.split("/"),
path |> String.split("/")
)

def path_matches_endpoint?(:object, endpoint, path),
def path_matches_endpoint?(aggregation, endpoint, path),
do:
path_matches_endpoint?(
endpoint |> Mapping.normalize_endpoint() |> String.split("/") |> Enum.drop(-1),
InterfaceGenerator.endpoint_by_aggregation(aggregation, endpoint)
|> Mapping.normalize_endpoint()
|> String.split("/"),
path |> String.split("/")
)

Expand All @@ -143,6 +128,16 @@ defmodule Astarte.Core.Generators.Mapping.Value do

defp path_matches_endpoint?(_, _), do: false

@doc false
@spec path_from_endpoint(endpoint :: String.t()) :: StreamData.t(String.t())
def path_from_endpoint(endpoint) do
endpoint
|> String.split("/")
|> Enum.map(&convert_token/1)
|> fixed_list()
|> map(&Enum.join(&1, "/"))
end

@doc """
Returns type and value once given the Value package and the full search path.
"""
Expand Down Expand Up @@ -172,4 +167,11 @@ defmodule Astarte.Core.Generators.Mapping.Value do
do: %{type: type, value: value}

defp type_value_from_path(search_path, [_ | tail]), do: type_value_from_path(search_path, tail)

defp convert_token(token) do
case Mapping.is_placeholder?(token) do
true -> string(:alphanumeric, min_length: 1)
false -> constant(token)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ defmodule Astarte.Core.Generators.Triggers.SimpleEvents.DeviceConnectedEvent do
alias Astarte.Core.Triggers.SimpleEvents.DeviceConnectedEvent

alias Astarte.Common.Generators.Ip, as: IpGenerator
alias Astarte.Utilities.Map, as: MapUtilities

@spec device_connected_event() :: StreamData.t(DeviceConnectedEvent.t())
@spec device_connected_event(keyword :: keyword()) :: StreamData.t(DeviceConnectedEvent.t())
Expand All @@ -40,25 +39,6 @@ defmodule Astarte.Core.Generators.Triggers.SimpleEvents.DeviceConnectedEvent do
end
end

@doc """
Convert this struct/stream to changes
"""
@spec to_changes(DeviceConnectedEvent.t()) :: StreamData.t(map())
def to_changes(data) when not is_struct(data, StreamData),
do: data |> constant() |> to_changes()

@spec to_changes(StreamData.t(DeviceConnectedEvent.t())) :: StreamData.t(map())
def to_changes(gen) do
gen all %DeviceConnectedEvent{
device_ip_address: device_ip_address
} <- gen do
%{
device_ip_address: device_ip_address
}
|> MapUtilities.clean()
end
end

defp device_ip_address,
do:
IpGenerator.ip(:ipv4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,6 @@ defmodule Astarte.Core.Generators.Triggers.SimpleEvents.DeviceDisconnectedEvent

alias Astarte.Core.Triggers.SimpleEvents.DeviceDisconnectedEvent

alias Astarte.Utilities.Map, as: MapUtilities

@spec device_disconnected_event() :: StreamData.t(DeviceDisconnectedEvent.t())
def device_disconnected_event, do: constant(%DeviceDisconnectedEvent{})

@doc """
Convert this struct/stream to changes
"""
@spec to_changes(DeviceDisconnectedEvent.t()) :: StreamData.t(map())
def to_changes(data) when not is_struct(data, StreamData),
do: data |> constant() |> to_changes()

@spec to_changes(StreamData.t(DeviceDisconnectedEvent.t())) :: StreamData.t(map())
def to_changes(gen) do
gen all device_disconnected_event <- gen do
device_disconnected_event
|> Map.from_struct()
|> MapUtilities.clean()
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ defmodule Astarte.Core.Generators.Triggers.SimpleEvents.DeviceErrorEvent do

alias Astarte.Core.Triggers.SimpleEvents.DeviceErrorEvent

alias Astarte.Utilities.Map, as: MapUtilities

@spec device_error_event() :: StreamData.t(DeviceErrorEvent.t())
@spec device_error_event(keyword :: keyword()) :: StreamData.t(DeviceErrorEvent.t())
def device_error_event(params \\ []) do
Expand All @@ -41,27 +39,6 @@ defmodule Astarte.Core.Generators.Triggers.SimpleEvents.DeviceErrorEvent do
end
end

@doc """
Convert this struct/stream to changes
"""
@spec to_changes(DeviceErrorEvent.t()) :: StreamData.t(map())
def to_changes(data) when not is_struct(data, StreamData),
do: data |> constant() |> to_changes()

@spec to_changes(StreamData.t(DeviceErrorEvent.t())) :: StreamData.t(map())
def to_changes(gen) do
gen all %DeviceErrorEvent{
error_name: error_name,
metadata: metadata
} <- gen do
%{
error_name: error_name,
metadata: metadata
}
|> MapUtilities.clean()
end
end

defp error_name, do: one_of([nil, string(:utf8)])

defp metadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,30 @@ defmodule Astarte.Core.Generators.Triggers.SimpleEvents.IncomingDataEvent do

import Astarte.Generators.Utilities.ParamsGen

alias Astarte.Core.Triggers.SimpleEvents.IncomingDataEvent

alias Astarte.Core.Interface
alias Astarte.Core.Triggers.SimpleEvents.IncomingDataEvent

alias Astarte.Core.Generators.Interface, as: InterfaceGenerator
alias Astarte.Core.Generators.Mapping.BSONValue, as: BSONValueGenerator
alias Astarte.Core.Generators.Mapping.Value, as: ValueGenerator

@spec incoming_data_event() :: StreamData.t(IncomingDataEvent.t())
@spec incoming_data_event(keyword :: keyword()) :: StreamData.t(IncomingDataEvent.t())
def incoming_data_event(params \\ []) do
gen_fields =
params gen all :interface,
%Interface{name: interface_name} = interface <-
InterfaceGenerator.interface(),
value <- ValueGenerator.value(interface: interface),
params: params do
%{
interface: interface_name,
path: value.path,
bson_value: value.value
}
end

gen_fields
|> bind(fn fields ->
fields
|> Map.new(fn {k, v} -> {k, constant(v)} end)
|> optional_map()
end)
|> map(&struct(IncomingDataEvent, &1))
params gen all :_,
%Interface{name: name} = interface <- InterfaceGenerator.interface(),
:_,
%{path: path} = package <- ValueGenerator.value(interface: interface),
:interface,
interface_name <- constant(name),
path <- constant(path),
bson_value <- BSONValueGenerator.to_bson(%{package | path: path}),
params: params do
%IncomingDataEvent{
interface: interface_name,
path: path,
bson_value: bson_value
}
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# This file is part of Astarte.
#
# Copyright 2025 SECO Mind Srl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

defmodule Astarte.Core.Generators.Triggers.SimpleEvents.InterfaceAddedEvent do
@moduledoc """
This module provides generators for Astarte Trigger Simple Event InterfaceAddedEvent struct.
"""
use ExUnitProperties

import Astarte.Generators.Utilities.ParamsGen

alias Astarte.Core.Triggers.SimpleEvents.InterfaceAddedEvent

alias Astarte.Core.Generators.Interface, as: InterfaceGenerator

@spec interface_added_event() :: StreamData.t(InterfaceAddedEvent.t())
@spec interface_added_event(keyword :: keyword()) :: StreamData.t(InterfaceAddedEvent.t())
def interface_added_event(params \\ []) do
params gen all interface <- InterfaceGenerator.name(),
major_version <- InterfaceGenerator.major_version(),
minor_version <- InterfaceGenerator.minor_version(major_version),
params: params do
%InterfaceAddedEvent{
interface: interface,
major_version: major_version,
minor_version: minor_version
}
end
end
end
Loading
Loading