mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 06:31:35 +08:00
WoT: documentation, XML-doc and member-order hygiene for conversion extraction
Comment/doc and member-order changes only; no behavior, oracle, default or limit changes.
This commit is contained in:
@@ -52,6 +52,7 @@ For current protocol-runtime architecture and the contributor guide for adding a
|
||||
- [12.4.1 Parent placement through `uav:componentOf`](#1241-parent-placement-through-uavcomponentof)
|
||||
- [12.4.2 Event notifier behaviour in projection Views](#1242-event-notifier-behaviour-in-projection-views)
|
||||
- [12.4.3 Carried dependencies and View membership](#1243-carried-dependencies-and-view-membership)
|
||||
- [12.4.4 Archive context for single-document conversion](#1244-archive-context-for-single-document-conversion)
|
||||
- [12.5 Portable identifiers](#125-portable-identifiers)
|
||||
- [12.6 The 1.02 asset surface](#126-the-102-asset-surface)
|
||||
|
||||
@@ -1253,6 +1254,38 @@ support-only in the outer. The View builder organizes only affordances that are
|
||||
support-only, so the View lists the selected measurements and not their units unless
|
||||
the units were selected.
|
||||
|
||||
### 12.4.4 Archive context for single-document conversion
|
||||
|
||||
A document of a partitioned NodeSet export states references whose authoritative facts
|
||||
(a reverse reference, a symmetric ReferenceType) live in sibling partitions. A caller
|
||||
that converts one document at a time supplies those siblings through
|
||||
`IWotArchiveContextProvider` backed by a `WotNodeSetConverter.ArchiveContextIndex`.
|
||||
The index groups partitions by their declared model identity (URI, version, publication
|
||||
date) together with the namespace table, never by the namespace table alone. An archive
|
||||
that declares no model has no authoritative identity and neither contributes to nor
|
||||
receives a context. Partitions of a group must agree on server URIs and shared aliases,
|
||||
and a group in which two partitions declare the same NodeId (in any equivalent
|
||||
spelling) is conflicted, so asking for its context fails instead of letting one sibling
|
||||
override another. A returned context is a completed, read-only snapshot. An index whose
|
||||
build was cut short by a bound is marked incomplete, so a document that carries
|
||||
preserved native nodes fails explicitly rather than validating against a subset.
|
||||
|
||||
A missing native event is accepted
|
||||
only when both of these hold: the owning preserved type declares it through a native
|
||||
`GeneratesEvent` reference (directly or through its type or supertype chain), and a
|
||||
bounded, validated selected EventType carrier supplies an identity equal to the
|
||||
normalized authored native target. When the native node is present it is compared in
|
||||
full, and an ambiguous native key or a missing map stays strict. A readable document
|
||||
is never archive authority by itself.
|
||||
|
||||
The resolver document budget (`WotNodeSetConverterOptions.MaxResolverDocuments`)
|
||||
defaults to 256 documents. The aggregation sample states a larger budget (1024) because
|
||||
its corpus exceeds the default; hosts with a larger corpus must do the same. The
|
||||
canonical corpus inventory lists 540 JSON documents, and the verified registry
|
||||
manifest holds 535 documents (394 Thing Descriptions and 141 Thing Models). These
|
||||
statements describe the conversion contract only. Complete XML validation and full
|
||||
JSON Schema validation remain unclaimed and deferred.
|
||||
|
||||
### 12.5 Portable identifiers
|
||||
|
||||
Two identifier forms are errors, because a document carrying either binds to the
|
||||
|
||||
@@ -879,32 +879,6 @@ namespace Opc.Ua.Wot
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an affordance of this document only exists as structural support for
|
||||
/// another affordance of the same document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A resolved projection carries the affordances that a selected affordance depends on, for
|
||||
/// example the unit property named by <c>uav:unitProperty</c>, so the resolved document
|
||||
/// stays self-contained. Such a dependency was not selected by the projection itself.
|
||||
/// An affordance that the projection selects explicitly is never support-only, even when
|
||||
/// another selected affordance also depends on it. The state is not part of the JSON
|
||||
/// document, is set only by the projection resolver and is always <c>false</c> for
|
||||
/// documents that were not produced by one.
|
||||
/// </remarks>
|
||||
/// <param name="kind">The WoT affordance kind.</param>
|
||||
/// <param name="name">The affordance name within its kind.</param>
|
||||
/// <returns><c>true</c> when the affordance is carried only as a dependency.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
|
||||
public bool IsSupportOnly(WotAffordanceKind kind, string name)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
return m_supportOnly != null && m_supportOnly.Contains((kind, name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>securityDefinitions</c> map, if present.
|
||||
/// </summary>
|
||||
@@ -953,6 +927,32 @@ namespace Opc.Ua.Wot
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an affordance of this document only exists as structural support for
|
||||
/// another affordance of the same document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A resolved projection carries the affordances that a selected affordance depends on, for
|
||||
/// example the unit property named by <c>uav:unitProperty</c>, so the resolved document
|
||||
/// stays self-contained. Such a dependency was not selected by the projection itself.
|
||||
/// An affordance that the projection selects explicitly is never support-only, even when
|
||||
/// another selected affordance also depends on it. The state is not part of the JSON
|
||||
/// document, is set only by the projection resolver and is always <c>false</c> for
|
||||
/// documents that were not produced by one.
|
||||
/// </remarks>
|
||||
/// <param name="kind">The WoT affordance kind.</param>
|
||||
/// <param name="name">The affordance name within its kind.</param>
|
||||
/// <returns><c>true</c> when the affordance is carried only as a dependency.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
|
||||
public bool IsSupportOnly(WotAffordanceKind kind, string name)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
return m_supportOnly != null && m_supportOnly.Contains((kind, name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the <c>uav:nodeSet</c> preservation envelope.
|
||||
/// </summary>
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace Opc.Ua.Wot
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the native NodeSet context for the document, or <c>null</c>
|
||||
/// when the document has no siblings that share its namespace table.
|
||||
/// when the document declares no model identity or has no siblings
|
||||
/// that share its declared model and namespace table.
|
||||
/// </summary>
|
||||
/// <param name="document">The document being converted.</param>
|
||||
/// <returns>The context, or <c>null</c>.</returns>
|
||||
@@ -241,23 +242,54 @@ namespace Opc.Ua.Wot
|
||||
key.Append(value.Length).Append(':').Append(value).Append(';');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The partitions of one authoritative model and their merged aliases and identities.
|
||||
/// </summary>
|
||||
private sealed class Group
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a group whose server URIs and model identity come from the first partition.
|
||||
/// </summary>
|
||||
/// <param name="header">The first partition, which supplies the group header.</param>
|
||||
public Group(UANodeSet header)
|
||||
{
|
||||
Header = header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first partition, against which later partitions are compared.
|
||||
/// </summary>
|
||||
public UANodeSet Header { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aliases shared by the partitions of the group.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Aliases { get; } = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preserved native nodes of all partitions.
|
||||
/// </summary>
|
||||
public List<UANode> Items { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the canonical identities of the nodes already contributed.
|
||||
/// </summary>
|
||||
public HashSet<string> NodeIds { get; } = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the partitions declare conflicting server URIs, aliases or identities.
|
||||
/// </summary>
|
||||
public bool Conflicted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the completed context snapshot, which is never mutated once built.
|
||||
/// </summary>
|
||||
public UANodeSet? Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Merges the server URIs and aliases of a partition, flagging any conflict.
|
||||
/// </summary>
|
||||
/// <param name="part">The partition to merge.</param>
|
||||
public void Merge(UANodeSet part)
|
||||
{
|
||||
if (!(part.ServerUris ?? []).SequenceEqual(Header.ServerUris ?? [], StringComparer.Ordinal))
|
||||
@@ -278,8 +310,6 @@ namespace Opc.Ua.Wot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UANodeSet? Context { get; set; }
|
||||
}
|
||||
|
||||
private readonly System.Threading.Lock m_lock = new();
|
||||
|
||||
@@ -490,6 +490,9 @@ namespace Opc.Ua.WotCon.Tests.Materialization
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that selecting only a value organizes only it, not the unit it depends on.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ExplicitSelectionOfAValueOrganizesOnlyItAndNotItsUnitDependency()
|
||||
{
|
||||
@@ -505,6 +508,9 @@ namespace Opc.Ua.WotCon.Tests.Materialization
|
||||
Assert.That(result.Plan.Omissions.Count, Is.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the resolved conversion document still carries the unit dependency of a value-only selection.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ResolvedDocumentOfAValueOnlySelectionStillCarriesTheUnitDependency()
|
||||
{
|
||||
@@ -533,6 +539,9 @@ namespace Opc.Ua.WotCon.Tests.Materialization
|
||||
"The conversion document keeps the unit dependency and its HasProperty source.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that explicitly selecting a value and its unit organizes both.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ExplicitSelectionOfAValueAndItsUnitOrganizesBoth()
|
||||
{
|
||||
@@ -547,6 +556,9 @@ namespace Opc.Ua.WotCon.Tests.Materialization
|
||||
"another selected Property also depends on it.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that selecting all of a source organizes the value and its unit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SelectingAllOfASourceOrganizesTheValueAndItsUnit()
|
||||
{
|
||||
|
||||
@@ -854,18 +854,27 @@ namespace Opc.Ua.WotCon.Tests.Samples
|
||||
return (registry.Current, contents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A resolver that supplies a native archive context and resolves no external references.
|
||||
/// </summary>
|
||||
private sealed class ContextResolver : IWotThingResolver, IWotArchiveContextProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the resolver over a context index.
|
||||
/// </summary>
|
||||
/// <param name="contexts">The index that supplies the native contexts.</param>
|
||||
public ContextResolver(WotNodeSetConverter.ArchiveContextIndex contexts)
|
||||
{
|
||||
m_contexts = contexts;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UANodeSet? GetArchiveContext(WotDocument document)
|
||||
{
|
||||
return m_contexts.Find(document);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<WotResolverResult> ResolveThingAsync(
|
||||
string reference,
|
||||
WotResolutionContext context,
|
||||
|
||||
@@ -310,8 +310,16 @@ namespace Opc.Ua.WotCon.Tests.Samples
|
||||
"samples", "WotCon", "AggregationClient", "Documents");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A resolver that serves the corpus documents and supplies a native archive context.
|
||||
/// </summary>
|
||||
private sealed class CorpusResolver : IWotThingResolver, IWotArchiveContextProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the resolver over the corpus documents and a context index.
|
||||
/// </summary>
|
||||
/// <param name="documents">The corpus documents keyed by normalized reference.</param>
|
||||
/// <param name="contexts">The index that supplies the native contexts.</param>
|
||||
public CorpusResolver(
|
||||
Dictionary<string, byte[]> documents,
|
||||
WotNodeSetConverter.ArchiveContextIndex contexts)
|
||||
@@ -320,11 +328,13 @@ namespace Opc.Ua.WotCon.Tests.Samples
|
||||
m_contexts = contexts;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UANodeSet? GetArchiveContext(WotDocument document)
|
||||
{
|
||||
return m_contexts.Find(document);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<WotResolverResult> ResolveThingAsync(
|
||||
string reference,
|
||||
WotResolutionContext context,
|
||||
|
||||
Reference in New Issue
Block a user