src/typesystem/TypeManager.ps1
# Copyright 2021, 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 TypeSchema) . (import-script TypeProvider) . (import-script ScalarTypeProvider) . (import-script CompositeTypeProvider) . (import-script TypeDefinition) . (import-script GraphObjectBuilder) . (import-script TypeSearcher) ScriptClass TypeManager { . {}.module.newboundscriptblock($::.TypeSchema.EnumScript) $graphContext = $null $graph = $null $definitions = $null $prototypes = $null $hasRequiredTypeDefinitions = $false $typeProviders = $null $typeSearcher = $null $id = $null function __initialize($graphContext) { $this.graphContext = $graphContext $this.graph = $::.GraphManager |=> GetGraph $this.graphContext $this.definitions = @{} $this.prototypes = @{ $false = @{} $true = @{} } $this.typeProviders = @{} $this.id = [guid]::newguid() } function GetPrototype($typeClass, $typeName, $fullyQualified = $false, $setDefaultValues = $false, $recursive = $false, $propertyFilter, [object[]] $valueList, $propertyList, $skipPropertyCheck, [bool] $isCollection) { $typeId = GetOptionallyQualifiedName $typeClass $typeName $fullyQualified $hasProperties = $propertyFilter -ne $null -or $propertyList -ne $null $prototype = if ( ! $hasProperties -and ! $isCollection ) { GetPrototypeFromCache $typeId $typeClass $setDefaultValues $recursive } if ( $hasProperties -or ! ( HasCacheKey $typeId $setDefaultValues $recursive ) ) { if ( ! $prototype ) { $type = FindTypeDefinition $typeClass $typeId $true $true $builder = new-so GraphObjectBuilder $this $type $setDefaultValues $recursive $propertyFilter $valueList $propertyList $skipPropertyCheck $result = $builder |=> ToObject $isCollection $prototype = $result.Value } if ( ! $hasProperties -and ! $isCollection ) { AddPrototypeToCache $typeId $type.Class $setDefaultValues $recursive $prototype } } [PSCustomObject] @{ Type = $typeId IsCollection = $isCollection ObjectProtoType = $prototype } } function SearchTypes([string] $typeNameTerm, [string[]] $criteria, [string[]] $typeClasses, [TypeIndexLookupClass] $lookupClass = 'Contains') { foreach ( $typeClass in $typeClasses ) { if ( $typeClass -notin $this.ScriptClass.SupportedTypeClasses ) { throw [ArgumentException]::new("The type class '$typeClass' is not supported for this command") } } if ( ! $typeClasses ) { throw 'No type classes specified for search -- at least one must be specified' } if ( ! $criteria ) { throw 'No search criteria were specified -- at least one criterion field must be specified' } __InitializeTypeSearcher $qualifiedName = if ( ! $typeNameTerm.Contains('.') ) { if ( $lookupClass -eq 'Exact' ) { foreach ( $class in $typeClasses ) { $nameForClass = GetOptionallyQualifiedName $class $typeNameTerm $false if ( $nameForClass ) { $nameForClass break } } } elseif ( $lookupClass -eq 'StartsWith' ) { "microsoft.graph." + $typeNameTerm } } $searchFields = foreach ( $criterion in $criteria ) { $normalizedSearchTerm = if ( $criterion -eq 'Name' -and $qualifiedName ) { $qualifiedName } else { $typeNameTerm } @{ Name = $criterion SearchTerm = $normalizedSearchTerm TypeClasses = $typeClasses LookupClass = $lookupClass } } $results = $this.typeSearcher |=> Search $searchFields if ( $lookupClass -ne 'Exact' -and 'Name' -in $criteria -and ! $typeNameTerm.Contains('.') ) { $qualifiedTypeName = GetOptionallyQualifiedName Entity $typeNameTerm $false foreach ( $result in $results ) { $isExactMatch = $qualifiedTypeName -eq $result.MatchedTypeName if ( $isExactMatch ) { $result.SetExactMatch('Name') break } } } $results | sort-object Score -descending } function GetTypeSearcher { __InitializeTypeSearcher $this.typeSearcher } function FindTypeDefinition($typeClass, $typeName, $fullyQualified, $errorIfNotFound = $false) { $definition = $null $classesLeftToTry = 1 $classes = if ( $typeClass -eq 'Unknown' ) { $orderedClasses = GetTypeClassPrecedence $classesLeftToTry = $orderedClasses.length $orderedClasses } else { [GraphTypeClass] $typeClass } foreach ( $class in $classes ) { $classesLeftToTry-- $typeId = GetOptionallyQualifiedName $class $typeName $fullyQualified $definition = $this.definitions[$typeId] if ( ! $definition ) { try { $definition = GetTypeDefinition $class $typeId $false ( ! $errorIfNotFound ) } catch { if ( $errorIfNotFound ) { if ( $typeClass -eq 'Unknown' ) { if ( $classesLeftToTry -eq 0 ) { throw "Type '$typeId' could not be found for any type class in Graph '$($this.graph.ApiVersion)'" } } else { throw } } } } if ( $definition ) { break } } if ( $errorIfNotFound -and ! $definition ) { throw "Unable to find type '$typeId' of type class '$typeClass'" } $definition } function GetTypeDefinition($typeClass, $typeId, $skipRequiredTypes, $ignoreIfNotFound) { $definition = $this.definitions[$typeId] if ( ! $definition ) { if ( ! $skipRequiredTypes ) { InitializeRequiredTypes } $typeProvider = __GetTypeProvider $typeClass $this.graph $type = $typeProvider |=> GetTypeDefinition $typeClass $typeId $ignoreIfNotFound if ( ! $type -and $ignoreIfNotFound ) { return } $requiredTypes = @($type) $baseTypeId = $type.BaseType while ( $baseTypeId ) { $baseTypeProvider = __GetTypeProvider $typeClass $this.graph $baseType = $baseTypeProvider.GetTypeDefinition('Unknown', $baseTypeId) $requiredTypes += $baseType $baseTypeId = if ( $baseType | gm BaseType -erroraction ignore ) { $basetype.BaseType } } for ( $typeIndex = $requiredTypes.length - 1; $typeIndex -ge 0; $typeIndex-- ) { $requiredType = $requiredTypes[$typeIndex] $requiredTypeId = $requiredType.typeId if ( ! $this.definitions[$requiredTypeId] ) { AddTypeDefinition $requiredTypeId $requiredType } } $definition = $this.definitions[$typeId] } $definition } enum PropertyType { Property NavigationProperty Method } function GetTypeDefinitionTransitiveProperties($typeDefinition, $propertyType = 'Property') { $properties = @() $validatedPropertyType = [PropertyType] $propertyType $propertyMember = if ( $validatedPropertyType -eq 'NavigationProperty' ) { 'NavigationProperties' } elseif ( $validatedPropertyType -eq 'Property' ) { 'Properties' } else { 'Methods' } if ( $typeDefinition.$propertyMember ) { $properties += $typeDefinition.$propertyMember } $visitedBaseTypes = @{} $baseTypeId = $typeDefinition.BaseType while ( $baseTypeId -and ! $visitedBaseTypes[$baseTypeId] ) { $visitedBaseTypes[$baseTypeId] = $true $baseTypeDefinition = FindTypeDefinition $typeDefinition.Class $baseTypeId $true if ( $baseTypeDefinition ) { $properties += $baseTypeDefinition.$PropertyMember $baseTypeId = $baseTypeDefinition.BaseType } else { $baseTypeId = $null } } $properties } function GetPrototypeId($typeId, $setDefaults, $recursive) { '{0}:{1}:{2}' -f $typeId, ([int32] $setDefaults), ([int32] $recursive) } function GetPrototypeFromCache($typeId, $typeClass, $setDefaults, $recursive) { $id = GetPrototypeId $typeId $setDefaults $recursive $cachedPrototype = $this.prototypes[$id] if ( $cachedPrototype ) { $foundTypeClass = $cachedPrototype['TypeClass'] if ( $foundTypeClass -ne $typeClass ) { if ( $typeClass -ne 'Unknown' ) { throw "Type '$typeId' was found with type class '$foundTypeClass' instead of required type class '$typeClass'" } } $cachedPrototype['Prototype'] } } function AddPrototypeToCache($typeId, $typeClass, $setDefaults, $recursive, $prototype) { $id = GetPrototypeId $typeId $setDefaults $recursive $this.prototypes.add($id, @{Prototype=$prototype;TypeClass=$typeClass}) } function HasCacheKey($typeId, $setDefaults, $recursive) { $id = GetPrototypeId $typeId $setDefaults $recursive $this.prototypes.ContainsKey($id) } function AddTypeDefinition($typeId, $type) { if ( $this.definitions[$typeId] ) { throw "Type '$typeId' already exists" } $this.definitions.Add($typeId, $type) } function InitializeRequiredTypes { if ( ! $this.hasRequiredTypeDefinitions ) { $requiredTypeInfo = $::.TypeProvider |=> GetRequiredTypeInfo $requiredTypeInfo | foreach { GetTypeDefinition $_.typeClass $_.typeId $true | out-null } $this.hasRequiredTypeDefinitions = $true } } function __InitializeTypeSearcher { if ( ! $this.typeSearcher ) { $providers = @{} foreach ( $class in $this.scriptclass.SupportedTypeClasses ) { $providers[$class.tostring()] = __GetTypeProvider $class $this.graph } $this.typeSearcher = new-so TypeSearcher $providers $null } } function GetTypeClassPrecedence { [GraphTypeClass]::Primitive, [GraphTypeClass]::Entity, [GraphTypeClass]::Complex, [GraphTypeClass]::Enumeration } function GetOptionallyQualifiedName($typeClass, $typeName, $isFullyQualified) { if ( $isFullyQualified ) { $this.graph |=> UnaliasQualifiedName $typeName } else { $typeNamespace = $::.TypeProvider.GetDefaultNamespace($typeClass, $this.graph) $::.TypeSchema.GetQualifiedTypeName($typeNamespace, $typeName) } } function __GetTypeProvider([GraphTypeClass] $typeClass) { $providerObjectClass = $::.TypeProvider |=> GetProviderForClass $typeClass __GetTypeProviderByObjectClass $providerObjectClass } function __GetTypeProviderByObjectClass($providerObjectClass) { $provider = $this.typeProviders[$providerObjectClass] if ( ! $provider ) { $provider = new-so $providerObjectClass $this.graph $this.typeProviders[$providerObjectClass] = $provider } $provider } static { . {}.module.newboundscriptblock($::.TypeSchema.EnumScript) const SupportedTypeClasses @([GraphTypeClass]::Entity, [GraphTypeClass]::Complex, [GraphTypeClass]::Enumeration) function Get($graphContext) { $manager = $graphContext |=> GetState $::.GraphManager.TypeStateKey if ( ! $manager ) { $graph = $::.GraphManager |=> GetGraph $graphContext $manager = new-so TypeManager $graphContext $graphContext |=> AddState $::.GraphManager.TypeStateKey $manager } $manager } function GetSortedTypeNames($allowedTypeClasses, $graphContext) { $typeClasses = if ( ! $allowedTypeClasses -or ( $allowedTypeClasses -eq 'Unknown' ) ) { 'Entity', 'Complex', 'Primitive', 'Enumeration' } else { , $allowedTypeClasses } $manager = Get $graphContext # TODO: The provider's GetSortedTypeNames should take in existing names # as a sorted list and add new items to avoid having to sort everything # when more than one type class is involved. $typeNames = foreach ( $targetTypeClass in $typeClasses ) { $typeProvider = $manager |=> __GetTypeProvider $targetTypeClass $typeProvider |=> GetSortedTypeNames $targetTypeClass } $result = if ( $typeClasses.length -eq 1 ) { $typeNames } else { # Individual type classes are sorted, but there is more than one # type class here, so we'll just sort everything. $typeNames | sort-object } $result } } } |