-
Notifications
You must be signed in to change notification settings - Fork 308
Expand file tree
/
Copy pathProtobufResolver.ts
More file actions
188 lines (171 loc) · 7.17 KB
/
ProtobufResolver.ts
File metadata and controls
188 lines (171 loc) · 7.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import { fail } from "node:assert";
import { ast, text, WithGeneration } from "@fern-api/csharp-codegen";
import { FernIr } from "@fern-fern/ir-sdk";
type ProtobufFile = FernIr.ProtobufFile;
type ProtobufService = FernIr.ProtobufService;
type ProtobufType = FernIr.ProtobufType;
type ServiceId = FernIr.ServiceId;
type TypeId = FernIr.TypeId;
type WellKnownProtobufType = FernIr.WellKnownProtobufType;
const WellKnownProtobufType = FernIr.WellKnownProtobufType;
import { GeneratorContext } from "../cli/index.js";
import { CsharpTypeMapper } from "../context/CsharpTypeMapper.js";
import { ResolvedWellKnownProtobufType } from "./ResolvedWellKnownProtobufType.js";
export class ProtobufResolver extends WithGeneration {
private csharpTypeMapper: CsharpTypeMapper;
public constructor(
private readonly context: GeneratorContext,
csharpTypeMapper: CsharpTypeMapper
) {
super(context.generation);
this.csharpTypeMapper = csharpTypeMapper;
}
public resolveWellKnownProtobufType(
wellKnownProtobufType: WellKnownProtobufType
): ResolvedWellKnownProtobufType | undefined {
for (const [typeId, typeDeclaration] of Object.entries(this.generation.ir.types)) {
if (this._isWellKnownProtobufType({ typeId, wellKnownProtobufTypes: [wellKnownProtobufType] })) {
return {
typeDeclaration,
wellKnownProtobufType
};
}
}
return undefined;
}
public getProtobufClassReference(typeId: TypeId): ast.ClassReference {
const protobufType =
this.getProtobufTypeForTypeId(typeId) ?? fail(`The type identified by ${typeId} is not a Protobuf type`);
switch (protobufType.type) {
case "wellKnown": {
return this.csharpTypeMapper.convertToClassReference(
this.model.dereferenceType(typeId).typeDeclaration
);
}
case "userDefined": {
const protoNamespace = this.context.protobufResolver.getNamespaceFromProtobufFile(protobufType.file);
const aliasSuffix = text.camelCase(
protoNamespace
.split(".")
.filter((segment) => !this.namespaces.root.split(".").includes(segment))
.join("_")
);
return this.csharp.classReference({
name: protobufType.name.originalName,
namespace: this.context.protobufResolver.getNamespaceFromProtobufFile(protobufType.file),
namespaceAlias: `Proto${aliasSuffix.charAt(0).toUpperCase()}${aliasSuffix.slice(1)}`
});
}
}
}
public getProtobufServiceForServiceId(serviceId: ServiceId): ProtobufService | undefined {
const transport = this.generation.ir.services[serviceId]?.transport;
if (transport == null) {
return undefined;
}
switch (transport.type) {
case "grpc":
return transport.service;
case "http":
return undefined;
}
}
public getNamespaceFromProtobufFile(protobufFile: ProtobufFile): string {
const namespace = protobufFile.options?.csharp?.namespace;
if (namespace == null) {
throw new Error(
`The 'csharp_namespace' file option must be declared in Protobuf file ${protobufFile.filepath}`
);
}
return namespace;
}
public isWellKnownProtobufType(typeId: TypeId): boolean {
return this._isWellKnownProtobufType({
typeId,
wellKnownProtobufTypes: [
WellKnownProtobufType.any(),
WellKnownProtobufType.struct(),
WellKnownProtobufType.value()
]
});
}
/**
* Returns true if the type is an external proto type (e.g. google.rpc.Status)
* that should be surfaced directly in the SDK without generating a wrapper type.
*/
public isExternalProtobufType(typeId: TypeId): boolean {
const protobufType = this.getProtobufTypeForTypeId(typeId);
if (protobufType?.type === "userDefined") {
const packageName = protobufType.file.packageName;
if (packageName != null && EXTERNAL_PROTO_PACKAGES.has(packageName)) {
return true;
}
}
// Fallback: match on the type's declared name for cases where
// proto source may not be fully resolved.
const typeDeclaration = this.generation.ir.types[typeId];
const typeName = typeDeclaration?.name?.name?.originalName;
return typeName != null && EXTERNAL_PROTO_TYPE_NAMES.has(typeName);
}
/**
* Returns the class reference for an external proto type, using
* known mappings to resolve the correct namespace and type name.
*/
public getExternalProtobufClassReference(typeId: TypeId): ast.ClassReference {
const typeDeclaration = this.generation.ir.types[typeId];
const typeName = typeDeclaration?.name?.name?.originalName;
const mapping = EXTERNAL_PROTO_TYPE_CLASS_REFERENCES[typeName ?? ""];
if (mapping != null) {
return this.csharp.classReference({
name: mapping.name,
namespace: mapping.namespace
});
}
// Fall back to the proto source if no known mapping exists.
return this.getProtobufClassReference(typeId);
}
public isWellKnownAnyProtobufType(typeId: TypeId): boolean {
return this._isWellKnownProtobufType({
typeId,
wellKnownProtobufTypes: [WellKnownProtobufType.any()]
});
}
private _isWellKnownProtobufType({
typeId,
wellKnownProtobufTypes
}: {
typeId: TypeId;
wellKnownProtobufTypes: WellKnownProtobufType[];
}): boolean {
const protobufType = this.getProtobufTypeForTypeId(typeId);
return (
protobufType?.type === "wellKnown" &&
wellKnownProtobufTypes.some(
(wellKnownProtobufType) => protobufType.value.type === wellKnownProtobufType.type
)
);
}
private getProtobufTypeForTypeId(typeId: TypeId): ProtobufType | undefined {
const typeDeclaration = this.generation.ir.types[typeId];
if (typeDeclaration?.source == null) {
return undefined;
}
return typeDeclaration.source.type === "proto" ? typeDeclaration.source.value : undefined;
}
}
/**
* Proto packages whose types should be surfaced directly in the SDK
* (using the proto-generated class) without generating a separate wrapper type.
*/
const EXTERNAL_PROTO_PACKAGES = new Set(["google.rpc"]);
/**
* Type names that correspond to external proto types. Used as a fallback
* when proto source info may not be fully resolved (e.g. from OpenAPI imports).
*/
const EXTERNAL_PROTO_TYPE_NAMES = new Set(["GoogleRpcStatus"]);
/**
* Known external proto type class references, keyed by IR type name.
*/
const EXTERNAL_PROTO_TYPE_CLASS_REFERENCES: Record<string, { name: string; namespace: string }> = {
GoogleRpcStatus: { name: "Status", namespace: "Google.Rpc" }
};