src/cmdlets/Get-GraphUri.ps1

# Copyright 2019, Adam Edwards
#
# 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.

. (import-script ../metadata/GraphManager)
. (import-script common/SegmentHelper)
. (import-script common/GraphUriParameterCompleter)

function Get-GraphUri {
    [cmdletbinding()]
    param(
        [parameter(parametersetname='FromUriParents', position=0, mandatory=$true)]
        [parameter(parametersetname='FromUriChildren', position=0, mandatory=$true)]
        [parameter(parametersetname='FromUri', position=0, mandatory=$true)]
        [Uri] $Uri,

        [parameter(parametersetname='FromUriChildren', mandatory=$true)]
        [parameter(parametersetname='FromObjectChildren', mandatory=$true)]
        [Switch] $Children,

        [parameter(parametersetname='FromUriChildren')]
        [parameter(parametersetname='FromObjectChildren')]
        [Switch] $LocatableChildren,

        [parameter(parametersetname='FromUriChildren')]
        [parameter(parametersetname='FromObjectChildren')]
        [uint16] $RecursionDepth = 1,

        [parameter(parametersetname='FromUriChildren')]
        [parameter(parametersetname='FromObjectChildren')]
        [Switch] $IncludeVirtualChildren,

        [parameter(parametersetname='FromUriParents', mandatory=$true)]
        [parameter(parametersetname='FromObjectParents', mandatory=$true)]
        [Switch] $Parents,

        [parameter(parametersetname='FromUriChildren')]
        [parameter(parametersetname='FromObjectChildren')]
        [Switch] $NoCycles,

        [Switch] $IgnoreMissingMetadata,

        [parameter(parametersetname='FromObject')]
        [String] $GraphScope = $null,

        [parameter(parametersetname='FromObjectParents', valuefrompipeline=$true, mandatory=$true)]
        [parameter(parametersetname='FromObjectChildren', valuefrompipeline=$true, mandatory=$true)]
        [parameter(parametersetname='FromObject', valuefrompipeline=$true, mandatory=$true)]
        [PSCustomObject[]]$GraphItems
    )

    # This is not very honest -- we're using valuefrompipeline, but
    # only to signal the presence of input -- we use $input because
    # unless you use BEGIN, END, PROCESS blocks, you can't actually
    # iterate the parameter -- $input is a way around that
    $inputs = if ( $graphItems ) {
        $input
    } else {
        $Uri
    }

    $results = @()

    $nextUris = new-object System.Collections.Generic.Queue[object[]]
    $inputs | foreach { $nextUris.Enqueue(@(0, $_)) }

    $DisallowedLocationClasses = if ( ! $IncludeVirtualChildren.IsPresent ) {
        @('EntityType')
    } else {
        @()
    }

    $validLocationClasses = if ( $LocatableChildren.ispresent ) {
        $allLocationClasses = $::.SegmentHelper.GetValidLocationClasses()
        $allLocationClasses | where { $_ -notin $DisallowedLocationClasses }
    } else {
        $null
    }

    $disallowVirtualChildren = $Children.ispresent -and ! $IncludeVirtualChildren.ispresent

    $context = if ( $GraphScope ) {
        $::.Logicalgraphmanager.Get().contexts[$GraphScope].context
    }

    while ( $nextUris.Count -gt 0 ) {
        $currentItem = $nextUris.Dequeue()
        $currentDepth = $currentItem[0] + 1
        $currentUri = $currentItem[1]

        $graphItem = if ($GraphItems) {
            $currentUri
        }

        $uriSource = $currentUri
        $inputUri = if ( $graphItem ) {
            $unparsedUri = if ( $graphItem | gm -membertype scriptmethod '__ItemContext' ) {
                [Uri] ($graphItem |=> __ItemContext | select -expandproperty RequestUri)
            } elseif ( $graphItem | gm uri ) {
                $uriSource = $graphItem.uri
                [Uri] $uriSource
            } else{
                throw "Object type does not support Graph URI source"
            }

            $parsedUri = $::.GraphUtilities |=> ParseGraphRelativeLocation $unparsedUri
            $context = $parsedUri.Context
            $parsedUri.GraphRelativeUri
        } else {
            $parsedLocation = $::.GraphUtilities |=> ParseGraphRelativeLocation $currentUri
            $context = $parsedLocation.context
            $parsedLocation.GraphRelativeUri
        }

        $parser = new-so SegmentParser $context $null ($graphItems -ne $null)

        write-verbose "Uri '$uriSource' translated to '$inputUri'"

        $mustIgnoreMissingMetadata = $IgnoreMissingMetadata.IsPresent -or (__Preference__MustWaitForMetadata)

        $contextReady = ($::.GraphManager |=> GetMetadataStatus $context) -eq [MetadataStatus]::Ready

        if ( $mustIgnoreMissingMetadata -and ! $contextReady ) {
            if ( ! $Children.IsPresent ) {
                return $::.SegmentHelper |=> ToPublicSegment $parser $::.GraphSegment.NullSegment
            }
            return @()
        }

        $segments = $::.SegmentHelper |=> UriToSegments $parser $inputUri
        $lastSegment = $segments | select -last 1

        $segmentTable = $null
        if ( $NoCycles.IsPresent ) {
            $segmentTable = @{}
            $segments | foreach { $segmentTable.Add($_.graphElement, $_) }
        }

        $instanceId = if ( $GraphItem ) {
            $typeData = ($lastSegment.graphElement |=> GetEntity).typedata
            if ( $typeData.IsCollection ) {
                if ( $graphItem | gm -membertype noteproperty id ) {
                    $graphItem.id
                } else {
                    $null
                }
            }
        }

        $lastPublicSegment = $::.SegmentHelper |=> ToPublicSegment $parser $lastSegment

        $count = if ( $Parents.ispresent ) {
            if ( $segments -is [Object[]] ) { $segments.length } else { 1 }
        } else {
            if ( $instanceId -or $Children.ispresent ) { 0 } else { 1 }
        }

        $segments | select -last $count | foreach {
            $results += ($::.SegmentHelper |=> ToPublicSegment $parser $_)
        }

        $childSegments = $null

        if ( $instanceId ) {
            $idSegment = $lastSegment |=> NewNextSegments ($context |=> GetGraph) $instanceId $validLocationClasses

            $additionalSegments = if ( $Children.IsPresent ) {
                $childSegments = $parser |=> GetChildren $idSegment $validLocationClasses | sort Name
            } else {
                # Create a new public segment since we are going to modify it
                $instanceSegment = ($::.SegmentHelper |=> ToPublicSegment $parser $idSegment $lastPublicSegment).psobject.copy()
                if ( $graphItem ) {
                    $::.SegmentHelper.AddContent($instanceSegment, $graphItem)
                }
                $instanceSegment
            }

            $additionalSegments | foreach {
                if ( ! $segmentTable -or $segmentTable[$_.graphElement] ) {
                    if ( $::.SegmentHelper.IsValidLocationClass($_.Class) -and ( $_.class -ne 'EntityType' ) ) {
                        $nextUris.Enqueue(@($currentDepth, $_.GraphUri))
                    }
                } else {
                    write-verbose "$($_.id) already exists in hierarchy $($_.GraphUri)"
                }
            }

            $results += $additionalSegments
        } elseif ( $Children.ispresent ) {
            $childSegments = $parser |=> GetChildren $lastSegment $validLocationClasses | sort Name
        } else {
            if ( $GraphItem ) {
                # Create a new public segment since we are going to modify it
                $lastOutputSegment = ($results | select -last 1).psobject.copy()
                $::.SegmentHelper.AddContent($lastOutputSegment, $graphItem)
                # Replace the segment in the collection with a new one
                # This is a rather convoluted approach :( -- needs a rewrite
                $results[$results.length - 1] = $lastOutputSegment
            }
        }

        if ( $childSegments ) {
            $publicChildSegments = @()
            $childSegments | foreach {
                $meetsLocationRequirement = ! $LocatableChildren.IsPresent -or $::.SegmentHelper.IsValidLocationClass(($_.graphElement |=> GetEntity).Type)
                $meetsNonvirtualRequirement = ! $disallowVirtualChildren -or ! $_.isVirtual
                $skipSegment = ! $meetsLocationRequirement -or ! $meetsNonvirtualRequirement
                if ( ! $skipSegment ) {
                    $publicChildSegments += ($::.SegmentHelper |=> ToPublicSegment $parser $_ $lastPublicSegment)
                }
            }

            if ( $currentDepth -lt $RecursionDepth ) {
                $publicChildSegments | foreach {
                    if ( ! $segmentTable -or ( ! $segmentTable[$_.details.graphElement] ) ) {
                        if ( $::.SegmentHelper.IsValidLocationClass($_.Class) -and ( $_.class -ne 'entitytype') ) {
                            $nextUris.Enqueue(@($currentDepth, $_.GraphUri))
                        }
                    } else {
                        write-verbose "$($_.id) already exists in hierarchy $($_.GraphUri)"
                    }
                }
            }

            $results += $publicChildSegments
        }
    }
    $results
}

$::.ParameterCompleter |=> RegisterParameterCompleter Get-GraphUri Uri (new-so GraphUriParameterCompleter ([GraphUriCompletionType]::AnyUri))