public async Task GetMetadataAsync()

in src/Azure.IIoT.OpcUa.Publisher/src/Services/NodeServices.cs [259:444]


        public async Task<NodeMetadataResponseModel> GetMetadataAsync(
            T endpoint, NodeMetadataRequestModel request, CancellationToken ct)
        {
            ArgumentNullException.ThrowIfNull(request);
            if (string.IsNullOrEmpty(request.NodeId) &&
                (request.BrowsePath == null || request.BrowsePath.Count == 0))
            {
                throw new ArgumentException("Node id or browse path missing", nameof(request));
            }
            using var trace = _activitySource.StartActivity("GetMetadata");
            return await _client.ExecuteAsync(endpoint, async context =>
            {
                var nodeId = await context.Session.ResolveNodeIdAsync(request.Header, request.NodeId,
                    request.BrowsePath, nameof(request.BrowsePath), _timeProvider,
                    context.Ct).ConfigureAwait(false);
                if (NodeId.IsNull(nodeId))
                {
                    throw new ArgumentException("Node id missing", nameof(request));
                }

                var (node, errorInfo) = await context.Session.ReadNodeAsync(
                    request.Header.ToRequestHeader(_timeProvider), nodeId,
                    request.Header.GetNamespaceFormat(_options), ct: context.Ct).ConfigureAwait(false);
                if (errorInfo != null || node.NodeClass == null)
                {
                    return new NodeMetadataResponseModel
                    {
                        ErrorInfo = errorInfo ?? ((StatusCode)StatusCodes.BadNodeIdUnknown)
                            .CreateResultModel()
                    };
                }

                VariableMetadataModel? variableMetadata = null;
                MethodMetadataModel? methodMetadata = null;
                DataTypeMetadataModel? dataTypeMetadata = null;
                var typeId = nodeId;
                if (node.NodeClass == NodeClass.Method)
                {
                    (methodMetadata, errorInfo) = await context.Session.GetMethodMetadataAsync(
                        request.Header.ToRequestHeader(_timeProvider), nodeId,
                        request.Header.GetNamespaceFormat(_options), context.Ct).ConfigureAwait(false);
                    if (errorInfo != null)
                    {
                        return new NodeMetadataResponseModel { ErrorInfo = errorInfo };
                    }
                }
                else if (node.NodeClass == NodeClass.DataType)
                {
                    // TODO
                    dataTypeMetadata = null;
                }
                else if (node.NodeClass is NodeClass.Variable or NodeClass.Object)
                {
                    // Get type definition
                    var (references, ei) = await context.Session.FindAsync(
                        request.Header.ToRequestHeader(_timeProvider), nodeId.YieldReturn(),
                        ReferenceTypeIds.HasTypeDefinition, maxResults: 1, ct: context.Ct).ConfigureAwait(false);
                    typeId = references.FirstOrDefault(r => r.ErrorInfo == null).Node;
                    if (NodeId.IsNull(typeId))
                    {
                        typeId = nodeId;
                    }
                    if (node.NodeClass == NodeClass.Variable)
                    {
                        (variableMetadata, errorInfo) = await context.Session.GetVariableMetadataAsync(
                            request.Header.ToRequestHeader(_timeProvider), nodeId,
                            request.Header.GetNamespaceFormat(_options), context.Ct).ConfigureAwait(false);
                        if (errorInfo != null)
                        {
                            return new NodeMetadataResponseModel { ErrorInfo = errorInfo };
                        }
                    }
                }

                var type = node;
                if (typeId != nodeId)
                {
                    (type, errorInfo) = await context.Session.ReadNodeAsync(
                        request.Header.ToRequestHeader(_timeProvider), typeId,
                        request.Header.GetNamespaceFormat(_options), ct: context.Ct).ConfigureAwait(false);
                    if (errorInfo != null || type.NodeClass == null)
                    {
                        return new NodeMetadataResponseModel
                        {
                            ErrorInfo = errorInfo ?? ((StatusCode)StatusCodes.BadTypeDefinitionInvalid)
                                .CreateResultModel()
                        };
                    }
                }

                // process the types starting from the top of the tree.
                var map = new Dictionary<ImmutableRelativePath, InstanceDeclarationModel>();
                var declarations = new List<InstanceDeclarationModel>();

                var hierarchy = new List<(NodeId, ReferenceDescription)>();
                await context.Session.CollectTypeHierarchyAsync(request.Header.ToRequestHeader(_timeProvider),
                    typeId, hierarchy, context.Ct).ConfigureAwait(false);
                hierarchy.Reverse(); // Start from Root super type
                foreach (var (subType, superType) in hierarchy)
                {
                    errorInfo = await context.Session.CollectInstanceDeclarationsAsync(
                        request.Header.ToRequestHeader(_timeProvider), (NodeId)superType.NodeId,
                        null, declarations, map, request.Header.GetNamespaceFormat(_options),
                        ct: context.Ct).ConfigureAwait(false);
                    if (errorInfo != null)
                    {
                        break;
                    }
                }
                if (errorInfo == null)
                {
                    // collect the fields for the selected type.
                    errorInfo = await context.Session.CollectInstanceDeclarationsAsync(
                        request.Header.ToRequestHeader(_timeProvider), typeId, null,
                        declarations, map, request.Header.GetNamespaceFormat(_options),
                        ct: context.Ct).ConfigureAwait(false);
                }
                return new NodeMetadataResponseModel
                {
                    ErrorInfo = errorInfo,
                    Description = node.Description,
                    DisplayName = node.DisplayName,
                    NodeClass = node.NodeClass.Value,
                    NodeId = node.NodeId,
                    MethodMetadata = methodMetadata,
                    VariableMetadata = variableMetadata,
                    DataTypeMetadata = dataTypeMetadata,
                    TypeDefinition = errorInfo != null ? null : new TypeDefinitionModel
                    {
                        TypeDefinitionId = type.NodeId,
                        DisplayName = type.DisplayName,
                        BrowseName = type.BrowseName,
                        Description = type.Description,
                        TypeHierarchy = hierarchy.ConvertAll(e => new NodeModel
                        {
                            NodeId = request.Header.AsString(e.Item2.NodeId,
                                context.Session.MessageContext, _options),
                            DisplayName = e.Item2.DisplayName.AsString(),
                            BrowseName = request.Header.AsString(e.Item2.BrowseName,
                                context.Session.MessageContext, _options),
                            NodeClass = e.Item2.NodeClass.ToServiceType()
                        }),
                        NodeType = GetNodeType(hierarchy
                            .Select(r => (NodeId)r.Item2.NodeId)
                            .Append(typeId)
                            .ToList()),
                        Declarations = declarations
                    }
                };
            }, request.Header, ct).ConfigureAwait(false);

            static NodeType GetNodeType(List<NodeId> hierarchy)
            {
                if (hierarchy.Count > 1)
                {
                    if (hierarchy[1] == ObjectTypeIds.BaseEventType)
                    {
                        return NodeType.Event;
                    }
                    if (hierarchy[1] == ObjectTypeIds.BaseInterfaceType)
                    {
                        return NodeType.Interface;
                    }
                    if (hierarchy[1] == VariableTypeIds.PropertyType)
                    {
                        return NodeType.Property;
                    }
                    if (hierarchy[1] == VariableTypeIds.BaseDataVariableType)
                    {
                        return NodeType.DataVariable;
                    }
                }
                if (hierarchy.Count > 0)
                {
                    if (hierarchy[0] == VariableTypeIds.BaseVariableType)
                    {
                        return NodeType.Variable;
                    }
                    if (hierarchy[0] == ObjectTypeIds.BaseObjectType)
                    {
                        return NodeType.Object;
                    }
                }
                return NodeType.Unknown;
            }
        }