33
44import {
55 CodeModel ,
6+ CSharpEmitterContext ,
67 InputClient ,
78 InputModelType
89} from "@typespec/http-client-csharp" ;
910import {
1011 calculateResourceTypeFromPath ,
1112 ResourceMetadata ,
13+ ResourceOperationKind ,
1214 ResourceScope
1315} from "./resource-metadata.js" ;
14- import { DecoratorInfo } from "@azure-tools/typespec-client-generator-core" ;
1516import {
16- armResourceCreateOrUpdate ,
17- armResourceOperations ,
18- armResourceRead ,
17+ DecoratorInfo ,
18+ getClientType ,
19+ SdkClientType ,
20+ SdkContext ,
21+ SdkHttpOperation ,
22+ SdkMethod ,
23+ SdkModelType ,
24+ SdkServiceOperation
25+ } from "@azure-tools/typespec-client-generator-core" ;
26+ import {
27+ armResourceActionName ,
28+ armResourceCreateOrUpdateName ,
29+ armResourceDeleteName ,
30+ armResourceInternal ,
31+ armResourceListName ,
32+ armResourceReadName ,
33+ armResourceUpdateName ,
34+ parentResourceName ,
1935 resourceGroupResource ,
2036 resourceMetadata ,
2137 singleton ,
2238 subscriptionResource ,
2339 tenantResource
2440} from "./sdk-context-options.js" ;
41+ import { DecoratorApplication , Model , NoTarget } from "@typespec/compiler" ;
42+ import { AzureEmitterOptions } from "@azure-typespec/http-client-csharp" ;
43+
44+ export async function updateClients (
45+ codeModel : CodeModel ,
46+ sdkContext : CSharpEmitterContext
47+ ) {
48+ const serviceMethods = new Map < string , SdkMethod < SdkHttpOperation > > (
49+ getAllSdkClients ( sdkContext )
50+ . flatMap ( ( c ) => c . methods )
51+ . map ( ( obj ) => [ obj . crossLanguageDefinitionId , obj ] )
52+ ) ;
53+ const models = new Map < string , SdkModelType > (
54+ sdkContext . sdkPackage . models . map ( ( m ) => [ m . crossLanguageDefinitionId , m ] )
55+ ) ;
56+ const resourceModels = getAllResourceModels ( codeModel ) ;
57+
58+ const resourceModelMap = new Map < string , ResourceMetadata > (
59+ resourceModels . map ( ( m ) => [
60+ m . crossLanguageDefinitionId ,
61+ {
62+ resourceType : "" ,
63+ isSingleton : m . decorators ?. some ( ( d ) => d . name == singleton ) ?? false ,
64+ resourceScope : getResourceScope ( m ) ,
65+ methods : [ ] ,
66+ parentResource : getParentResourceModelId (
67+ sdkContext ,
68+ models . get ( m . crossLanguageDefinitionId )
69+ )
70+ } as ResourceMetadata
71+ ] )
72+ ) ;
2573
26- export function updateClients ( codeModel : CodeModel ) {
2774 // first we flatten all possible clients in the code model
2875 const clients = getAllClients ( codeModel ) ;
2976
30- // to fully calculation the resource metadata, we have to go with 2 passes
31- // in which the first pass we gather everything we could for each client
32- // the second pass we figure out there cross references between the clients (such as parent resource)
33- // then pass to update all the clients with their own information
34- const metadata : Map < InputClient , ResourceMetadata > = new Map ( ) ;
77+ // then we iterate over all the clients and their methods to find the resource operations
78+ // and add them to the resource model metadata
79+ // we also calculate the resource type from the path of the operation
3580 for ( const client of clients ) {
36- gatherResourceMetadata ( client , metadata ) ;
81+ for ( const method of client . methods ) {
82+ const serviceMethod = serviceMethods . get (
83+ method . crossLanguageDefinitionId
84+ ) ;
85+ const [ kind , modelId ] =
86+ parseResourceOperation ( serviceMethod , sdkContext ) ?? [ ] ;
87+ if ( modelId && kind ) {
88+ const entry = resourceModelMap . get ( modelId ) ;
89+ entry ?. methods . push ( {
90+ id : method . crossLanguageDefinitionId ,
91+ kind
92+ } ) ;
93+ if ( entry && ! entry . resourceType ) {
94+ entry . resourceType = calculateResourceTypeFromPath (
95+ method . operation . path
96+ ) ;
97+ }
98+ }
99+ }
37100 }
38101
39- // populate the parent resource information
102+ // the last step, add the decorator to the resource model
103+ for ( const model of resourceModels ) {
104+ const metadata = resourceModelMap . get ( model . crossLanguageDefinitionId ) ;
105+ if ( metadata ) {
106+ addResourceMetadata ( model , metadata ) ;
107+ }
108+ }
109+ }
40110
41- // the last step, add the decorator to the client
42- for ( const client of clients ) {
43- const resourceMetadata = metadata . get ( client ) ;
44- if ( resourceMetadata ) {
45- addResourceMetadata ( client , resourceMetadata ) ;
111+ function parseResourceOperation (
112+ serviceMethod : SdkMethod < SdkHttpOperation > | undefined ,
113+ sdkContext : CSharpEmitterContext
114+ ) : [ ResourceOperationKind , string | undefined ] | undefined {
115+ const decorators = serviceMethod ?. __raw ?. decorators ;
116+ for ( const decorator of decorators ?? [ ] ) {
117+ if ( decorator . definition ?. name === armResourceReadName ) {
118+ return [
119+ ResourceOperationKind . Get ,
120+ getResourceModelId ( sdkContext , decorator )
121+ ] ;
122+ } else if ( decorator . definition ?. name == armResourceCreateOrUpdateName ) {
123+ return [
124+ ResourceOperationKind . Create ,
125+ getResourceModelId ( sdkContext , decorator )
126+ ] ;
127+ } else if ( decorator . definition ?. name == armResourceUpdateName ) {
128+ return [
129+ ResourceOperationKind . Update ,
130+ getResourceModelId ( sdkContext , decorator )
131+ ] ;
132+ } else if ( decorator . definition ?. name == armResourceDeleteName ) {
133+ return [
134+ ResourceOperationKind . Delete ,
135+ getResourceModelId ( sdkContext , decorator )
136+ ] ;
137+ } else if ( decorator . definition ?. name == armResourceListName ) {
138+ return [
139+ ResourceOperationKind . List ,
140+ getResourceModelId ( sdkContext , decorator )
141+ ] ;
142+ } else if ( decorator . definition ?. name == armResourceActionName ) {
143+ return [
144+ ResourceOperationKind . Action ,
145+ getResourceModelId ( sdkContext , decorator )
146+ ] ;
46147 }
47148 }
149+ return undefined ;
48150}
49151
50- export function getAllClients ( codeModel : CodeModel ) : InputClient [ ] {
51- const clients : InputClient [ ] = [ ] ;
52- for ( const client of codeModel . clients ) {
152+ function getParentResourceModelId (
153+ sdkContext : CSharpEmitterContext ,
154+ model : SdkModelType | undefined
155+ ) : string | undefined {
156+ const decorators = ( model ?. __raw as Model ) ?. decorators ;
157+ const parentResourceDecorator = decorators ?. find (
158+ ( d ) => d . definition ?. name == parentResourceName
159+ ) ;
160+ return getResourceModelId ( sdkContext , parentResourceDecorator ) ?? undefined ;
161+ }
162+
163+ function getResourceModelId (
164+ sdkContext : CSharpEmitterContext ,
165+ decorator ?: DecoratorApplication
166+ ) : string | undefined {
167+ if ( ! decorator ) return undefined ;
168+ const model = getClientType (
169+ sdkContext ,
170+ decorator . args [ 0 ] . value as Model
171+ ) as SdkModelType ;
172+ if ( model ) {
173+ return model . crossLanguageDefinitionId ;
174+ } else {
175+ sdkContext . logger . reportDiagnostic ( {
176+ code : "general-error" ,
177+ message : `Resource model not found for decorator ${ decorator . decorator . name } ` ,
178+ target : NoTarget ,
179+ severity : "error"
180+ } ) ;
181+ return undefined ;
182+ }
183+ }
184+
185+ export function getAllSdkClients (
186+ sdkContext : SdkContext < AzureEmitterOptions , SdkHttpOperation >
187+ ) : SdkClientType < SdkServiceOperation > [ ] {
188+ const clients : SdkClientType < SdkServiceOperation > [ ] = [ ] ;
189+ for ( const client of sdkContext . sdkPackage . clients ) {
53190 traverseClient ( client ) ;
54191 }
55192
56193 return clients ;
57194
58- function traverseClient ( client : InputClient ) {
195+ function traverseClient ( client : SdkClientType < SdkServiceOperation > ) {
59196 clients . push ( client ) ;
60197 if ( client . children ) {
61198 for ( const child of client . children ) {
@@ -65,97 +202,65 @@ export function getAllClients(codeModel: CodeModel): InputClient[] {
65202 }
66203}
67204
68- function gatherResourceMetadata (
69- client : InputClient ,
70- metadataMap : Map < InputClient , ResourceMetadata >
71- ) {
72- // TODO: we can implement this decorator in TCGC until we meet the corner case
73- // if the client has resourceMetadata decorator, it is a resource client and we don't need to add it again
74- if ( client . decorators ?. some ( ( d ) => d . name == resourceMetadata ) ) {
75- return ;
205+ export function getAllClients ( codeModel : CodeModel ) : InputClient [ ] {
206+ const clients : InputClient [ ] = [ ] ;
207+ for ( const client of codeModel . clients ) {
208+ traverseClient ( client ) ;
76209 }
77210
78- // TODO: Once we have the ability to get resource hierarchy from TCGC directly, we can remove this implementation
79- // A resource client should have decorator armResourceOperations and contains either a get operation(containing armResourceRead deocrator) or a put operation(containing armResourceCreateOrUpdate decorator)
80- if (
81- client . decorators ?. some ( ( d ) => d . name == armResourceOperations ) &&
82- client . methods . some (
83- ( m ) =>
84- m . operation . decorators ?. some (
85- ( d ) => d . name == armResourceRead || armResourceCreateOrUpdate
86- )
87- )
88- ) {
89- let resourceModel : InputModelType | undefined = undefined ;
90- let isSingleton : boolean = false ;
91- let resourceType : string | undefined = undefined ;
92- // We will try to get resource metadata from put operation firstly, if not found, we will try to get it from get operation
93- const putOperation = client . methods . find (
94- ( m ) =>
95- m . operation . decorators ?. some ( ( d ) => d . name == armResourceCreateOrUpdate )
96- ) ?. operation ;
97- if ( putOperation ) {
98- const path = putOperation . path ;
99- resourceType = calculateResourceTypeFromPath ( path ) ;
100- resourceModel = putOperation . responses . filter ( ( r ) => r . bodyType ) [ 0 ]
101- . bodyType as InputModelType ;
102- isSingleton =
103- resourceModel . decorators ?. some ( ( d ) => d . name == singleton ) ?? false ;
104- } else {
105- const getOperation = client . methods . find (
106- ( m ) => m . operation . decorators ?. some ( ( d ) => d . name == armResourceRead )
107- ) ?. operation ;
108- if ( getOperation ) {
109- const path = getOperation . path ;
110- resourceType = calculateResourceTypeFromPath ( path ) ;
111- resourceModel = getOperation . responses . filter ( ( r ) => r . bodyType ) [ 0 ]
112- . bodyType as InputModelType ;
113- isSingleton =
114- resourceModel . decorators ?. some ( ( d ) => d . name == singleton ) ?? false ;
211+ return clients ;
212+
213+ function traverseClient ( client : InputClient ) {
214+ clients . push ( client ) ;
215+ if ( client . children ) {
216+ for ( const child of client . children ) {
217+ traverseClient ( child ) ;
115218 }
116219 }
220+ }
221+ }
117222
118- if ( resourceModel && resourceType ) {
119- // find the scope on its model
120- const metadata : ResourceMetadata = {
121- resourceModel : resourceModel ,
122- resourceClient : client ,
123- resourceType : resourceType ,
124- isSingleton : isSingleton ,
125- resourceScope : getResourceScope ( resourceModel )
126- } ;
127- metadataMap . set ( client , metadata ) ;
223+ function getAllResourceModels ( codeModel : CodeModel ) : InputModelType [ ] {
224+ const resourceModels : InputModelType [ ] = [ ] ;
225+ for ( const model of codeModel . models ) {
226+ if ( model . decorators ?. some ( ( d ) => d . name == armResourceInternal ) ) {
227+ model . crossLanguageDefinitionId ;
228+ resourceModels . push ( model ) ;
128229 }
129230 }
231+ return resourceModels ;
232+ }
130233
131- function getResourceScope ( model : InputModelType ) : ResourceScope {
132- const decorators = model . decorators ;
133- if ( decorators ?. some ( ( d ) => d . name == tenantResource ) ) {
134- return ResourceScope . Tenant ;
135- } else if ( decorators ?. some ( ( d ) => d . name == subscriptionResource ) ) {
136- return ResourceScope . Subscription ;
137- } else if ( decorators ?. some ( ( d ) => d . name == resourceGroupResource ) ) {
138- return ResourceScope . ResourceGroup ;
139- }
140- return ResourceScope . ResourceGroup ; // all the templates work as if there is a resource group decorator when there is no such decorator
234+ function getResourceScope ( model : InputModelType ) : ResourceScope {
235+ const decorators = model . decorators ;
236+ if ( decorators ?. some ( ( d ) => d . name == tenantResource ) ) {
237+ return ResourceScope . Tenant ;
238+ } else if ( decorators ?. some ( ( d ) => d . name == subscriptionResource ) ) {
239+ return ResourceScope . Subscription ;
240+ } else if ( decorators ?. some ( ( d ) => d . name == resourceGroupResource ) ) {
241+ return ResourceScope . ResourceGroup ;
141242 }
243+ return ResourceScope . ResourceGroup ; // all the templates work as if there is a resource group decorator when there is no such decorator
142244}
143245
144- function addResourceMetadata ( client : InputClient , metadata : ResourceMetadata ) {
246+ function addResourceMetadata (
247+ model : InputModelType ,
248+ metadata : ResourceMetadata
249+ ) {
145250 const resourceMetadataDecorator : DecoratorInfo = {
146251 name : resourceMetadata ,
147252 arguments : {
148- resourceModel : metadata . resourceModel . crossLanguageDefinitionId ,
149- resourceClient : metadata . resourceClient . crossLanguageDefinitionId ,
150253 isSingleton : metadata . isSingleton ,
151254 resourceType : metadata . resourceType ,
152- resourceScope : metadata . resourceScope
255+ resourceScope : metadata . resourceScope ,
256+ methods : metadata . methods ,
257+ parentResource : metadata . parentResource
153258 }
154259 } ;
155260
156- if ( ! client . decorators ) {
157- client . decorators = [ ] ;
261+ if ( ! model . decorators ) {
262+ model . decorators = [ ] ;
158263 }
159264
160- client . decorators . push ( resourceMetadataDecorator ) ;
265+ model . decorators . push ( resourceMetadataDecorator ) ;
161266}
0 commit comments