src/REST/GraphRequest.ps1

# Copyright 2020, 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 RESTRequest)
. (import-script GraphResponse)

ScriptClass GraphRequest {
    $Connection = $null
    $Uri = strict-val [Uri]
    $RelativeUri = strict-val [Uri]
    $Verb = strict-val [String]
    $Body = strict-val [String]
    $Query = $null
    $Headers = $null
    $ClientRequestId = $null
    $ReturnRequest = $false
    $DeltaQuery = $false
    $PageSizePreference = 0

    function __initialize([PSCustomObject] $GraphConnection, [Uri] $uri, $verb = 'GET', $headers = $null, $query = $null, $clientRequestId, [bool] $noRequestId, [bool] $returnRequest, [bool] $deltaQuery, $deltaToken, $pageSizePreference, [string] $consistencyLevel = 'Auto') {
        $targetConsistencyLevel = if ( $consistencyLevel -and $consistencyLevel -ne 'Auto' ) {
            $consistencyLevel
        } else {
            $graphConnection.consistencyLevel
        }

        if ( $targetConsistencyLevel -eq 'Auto' ) {
            $targetConsistencyLevel = $null
        }

        if ( $targetConsistencyLevel -and ( $targetConsistencyLevel -notin 'Default', 'Session', 'Eventual' ) ) {
            throw "The specified consistency level '$targetConsistencyLevel' is not valid -- it must be one of 'Auto', 'Default', 'Session', or 'Eventual'"
        }

        $uriString = if ( $uri.scheme -ne $null ) {
            $uri.AbsoluteUri
        } else {
            $graphConnection.GraphEndpoint.Graph.tostring() + $uri.originalstring
        }

        if ( ! $noRequestId ) {
            $this.ClientRequestId = if ( $clientRequestId ) {
                [guid] ($clientRequestId)
            } else {
                new-guid
            }
        }

        $uriQueryLength = if ( $uri.Query -ne $null ) { $uri.Query.length } else { 0 }
        $uriString = $uriString.substring(0, $uriString.length - $uriQueryLength)

        if ( $deltaQuery -or $deltaToken ) {
            if ( ! ( $::.GraphUtilities |=> IsDeltaUri $uri ) ) {
                $uriString = $uriString, 'microsoft.graph.delta' -join '/'
            }
        }

        $uriNoQuery = [Uri]::new($uriString)

        $this.PageSizePreference = $pageSizePreference
        $this.DeltaQuery = $deltaQuery
        $this.ReturnRequest = $returnRequest
        $this.Connection = $GraphConnection
        $this.RelativeUri = $uri
        $this.Uri = $uriNoQuery
        $this.Verb = $verb

        $queryParams = if ( ! $deltaToken ) {
            @($uri.query)
        } else {
            @("deltaToken=$deltaToken")
        }

        $queryParams += $query
        $this.Query = __AddQueryParameters $queryParams

        $this.Headers = if ( $headers -ne $null ) {
            $headers
        } else {
            @{'Content-Type'='application/json'}
        }

        if ( $this.ClientRequestId ) {
            $this.Headers['client-request-id'] = $this.ClientRequestId.tostring()
        }

        if ( $this.PageSizePreference ) {
            $this.Headers['Prefer'] = "Prefer: odata.maxpagesize=$($this.PageSizePreference)"
        }

        if ( $targetConsistencyLevel -and $targetConsistencyLevel -ne 'Default' ) {
            $this.Headers['ConsistencyLevel'] = $targetConsistencyLevel
        }
    }

    function Invoke($pageStartIndex = $null, $maxResultCount = $null, $logger) {
        if ( $this.Connection.Status -eq ([GraphConnectionStatus]::Offline) ) {
            throw "Web request cannot proceed -- connection status is set to offline"
        }

        if ( $this.Connection.Identity -and ! $this.Headers.ContainsKey('Authorization') ) {
            $token = $this.Connection |=> GetToken
            $this.Headers['Authorization'] = $token.CreateAuthorizationHeader()
        }

        $queryParameters = $this.query
        if ( $this.Query -isnot [object[]] ) {
            $queryParameters = , $this.Query
        }

        if ($pageStartIndex -ne $null) {
            $queryParameters += (__NewODataParameter 'skip' $pageStartIndex)
        }

        if ( $maxResultCount ) {
            $queryParameters += (__NewODataParameter 'top' $maxResultCount)
        }

        $query = __AddQueryParameters $queryParameters

        if ( $this.ClientRequestId ) {
            write-verbose "Invoking Graph request with request id: '$($this.ClientRequestId)'"
        }

        $response = __InvokeRequest $this.verb $this.uri $query $logger
        new-so GraphResponse $response
    }

    function SetBody($body) {
        $this.body = if ($body -is [string] ) {
            $body
        } else {
            $body | convertto-json -depth 6
        }
    }

    function __InvokeRequest($verb, $uri, $query, $logger) {
        $uriPath = __UriWithQuery $uri $query
        $uri = new-object Uri $uriPath
        $restRequest = new-so RESTRequest $uri $verb $this.headers $this.body $this.Connection.UserAgent $this.returnRequest
        $logEntry = if ( $logger -and ! $this.returnRequest ) { $logger |=> NewLogEntry $this.Connection $restRequest }
        try {
            $restResponse = $restRequest |=> Invoke -logEntry $logEntry
        } finally {
            if ( $logEntry ) { $logger |=> CommitLogEntry $logEntry }
        }
        $restResponse
    }

    function __AddQueryParameters([string[]] $parameters) {
        $components = @()

        $parameters | foreach {
            if ( $_ -ne $null ) {

                $normalizedParameter = if ( $_.startswith('?') ) {
                    $_.substring(1, $_.length -1)
                } else {
                    $_
                }

                if ( $normalizedParameter -ne $null -and $normalizedParameter.length -gt 0 ) {
                    $components += $normalizedParameter
                }
            }
        }

        $components -join '&'
    }

    function __UriWithQuery($uri, $query) {
        if ( $query -ne $null -and $query.length -gt 0 ) {
            new-object Uri ($Uri.tostring() + '?' + $query)
        } else {
            new-object Uri $uri.tostring()
        }
    }

    function __NewODataParameter($parameterName, $value) {
        if ( $value -ne $null ) {
            '${0}={1}' -f $parameterName, $value
        } else {
            '${0}' -f $parameterName
        }
    }
}