django-cfg 1.4.72__py3-none-any.whl → 1.4.74__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/endpoints/README.md +144 -0
- django_cfg/apps/api/endpoints/endpoints_status/__init__.py +13 -0
- django_cfg/apps/api/endpoints/urls.py +13 -6
- django_cfg/apps/api/endpoints/urls_list/__init__.py +10 -0
- django_cfg/apps/api/endpoints/urls_list/serializers.py +74 -0
- django_cfg/apps/api/endpoints/urls_list/views.py +231 -0
- django_cfg/apps/api/health/drf_views.py +9 -0
- django_cfg/apps/api/health/serializers.py +4 -0
- django_cfg/models/django/crypto_fields.py +11 -0
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/archive/manager.py +14 -0
- django_cfg/modules/django_client/core/generator/__init__.py +40 -2
- django_cfg/modules/django_client/core/generator/proto/__init__.py +17 -0
- django_cfg/modules/django_client/core/generator/proto/generator.py +461 -0
- django_cfg/modules/django_client/core/generator/proto/messages_generator.py +260 -0
- django_cfg/modules/django_client/core/generator/proto/services_generator.py +295 -0
- django_cfg/modules/django_client/core/generator/proto/test_proto_generator.py +262 -0
- django_cfg/modules/django_client/core/generator/proto/type_mapper.py +153 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +49 -3
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/METADATA +1 -1
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/RECORD +31 -20
- /django_cfg/apps/api/endpoints/{checker.py → endpoints_status/checker.py} +0 -0
- /django_cfg/apps/api/endpoints/{drf_views.py → endpoints_status/drf_views.py} +0 -0
- /django_cfg/apps/api/endpoints/{serializers.py → endpoints_status/serializers.py} +0 -0
- /django_cfg/apps/api/endpoints/{tests.py → endpoints_status/tests.py} +0 -0
- /django_cfg/apps/api/endpoints/{views.py → endpoints_status/views.py} +0 -0
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proto Messages Generator - Generates Protocol Buffer message definitions from IR schemas.
|
|
3
|
+
|
|
4
|
+
Converts IRSchemaObject instances into proto3 message definitions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from .type_mapper import ProtoTypeMapper
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from django_cfg.modules.django_client.core.ir.schema import IRSchemaObject
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProtoMessagesGenerator:
|
|
18
|
+
"""
|
|
19
|
+
Generates Protocol Buffer message definitions from IR schemas.
|
|
20
|
+
|
|
21
|
+
Handles:
|
|
22
|
+
- Basic message structure with fields
|
|
23
|
+
- Nested message definitions
|
|
24
|
+
- Enums (from string enums in OpenAPI)
|
|
25
|
+
- Field numbering
|
|
26
|
+
- Proper indentation and formatting
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, type_mapper: ProtoTypeMapper):
|
|
30
|
+
self.type_mapper = type_mapper
|
|
31
|
+
self.generated_messages: set[str] = set() # Track what we've generated
|
|
32
|
+
self.message_definitions: list[str] = [] # Ordered list of definitions
|
|
33
|
+
|
|
34
|
+
def generate_message(
|
|
35
|
+
self, schema: IRSchemaObject, message_name: str | None = None
|
|
36
|
+
) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Generate a proto message from an IR schema.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
schema: IR schema object to convert
|
|
42
|
+
message_name: Override message name (uses schema.name if not provided)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Proto message definition string
|
|
46
|
+
"""
|
|
47
|
+
if message_name is None:
|
|
48
|
+
message_name = self.type_mapper.get_message_name(schema.name or "Message")
|
|
49
|
+
|
|
50
|
+
# Skip if already generated
|
|
51
|
+
if message_name in self.generated_messages:
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
self.generated_messages.add(message_name)
|
|
55
|
+
|
|
56
|
+
# Handle different schema types
|
|
57
|
+
if schema.type == "object":
|
|
58
|
+
return self._generate_object_message(schema, message_name)
|
|
59
|
+
elif schema.type == "array":
|
|
60
|
+
# Arrays are handled as repeated fields, not separate messages
|
|
61
|
+
return ""
|
|
62
|
+
elif schema.enum:
|
|
63
|
+
return self._generate_enum(schema, message_name)
|
|
64
|
+
else:
|
|
65
|
+
# Scalar types don't need messages
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
def _generate_object_message(self, schema: IRSchemaObject, message_name: str) -> str:
|
|
69
|
+
"""Generate a message for an object schema."""
|
|
70
|
+
lines = [f"message {message_name} {{"]
|
|
71
|
+
|
|
72
|
+
# Generate nested enums first
|
|
73
|
+
for prop_name, prop_schema in (schema.properties or {}).items():
|
|
74
|
+
if prop_schema.enum:
|
|
75
|
+
enum_name = self.type_mapper.get_message_name(prop_name)
|
|
76
|
+
nested_enum = self._generate_enum(prop_schema, enum_name, indent=2)
|
|
77
|
+
if nested_enum:
|
|
78
|
+
lines.append("")
|
|
79
|
+
lines.extend(f" {line}" for line in nested_enum.split("\n"))
|
|
80
|
+
|
|
81
|
+
# Generate nested messages (only if not already defined at top level)
|
|
82
|
+
for prop_name, prop_schema in (schema.properties or {}).items():
|
|
83
|
+
if prop_schema.type == "object" and not prop_schema.enum:
|
|
84
|
+
nested_name = self.type_mapper.get_message_name(prop_name)
|
|
85
|
+
# Skip if this message is already generated (it's a top-level schema)
|
|
86
|
+
if nested_name not in self.generated_messages:
|
|
87
|
+
self.generated_messages.add(nested_name)
|
|
88
|
+
nested_msg = self._generate_object_message(prop_schema, nested_name)
|
|
89
|
+
if nested_msg:
|
|
90
|
+
lines.append("")
|
|
91
|
+
lines.extend(f" {line}" for line in nested_msg.split("\n"))
|
|
92
|
+
|
|
93
|
+
# Generate fields
|
|
94
|
+
field_number = 1
|
|
95
|
+
if schema.properties:
|
|
96
|
+
lines.append("")
|
|
97
|
+
for prop_name, prop_schema in schema.properties.items():
|
|
98
|
+
field_def = self._generate_field(
|
|
99
|
+
prop_name, prop_schema, field_number, schema.required or []
|
|
100
|
+
)
|
|
101
|
+
lines.append(f" {field_def}")
|
|
102
|
+
field_number += 1
|
|
103
|
+
|
|
104
|
+
lines.append("}")
|
|
105
|
+
|
|
106
|
+
definition = "\n".join(lines)
|
|
107
|
+
self.message_definitions.append(definition)
|
|
108
|
+
return definition
|
|
109
|
+
|
|
110
|
+
def _generate_field(
|
|
111
|
+
self,
|
|
112
|
+
field_name: str,
|
|
113
|
+
field_schema: IRSchemaObject,
|
|
114
|
+
field_number: int,
|
|
115
|
+
required_fields: list[str],
|
|
116
|
+
) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Generate a single field definition.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
field_name: Original field name
|
|
122
|
+
field_schema: Field schema
|
|
123
|
+
field_number: Proto field number
|
|
124
|
+
required_fields: List of required field names
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Field definition line (e.g., "optional string name = 1;")
|
|
128
|
+
"""
|
|
129
|
+
# Sanitize field name
|
|
130
|
+
proto_field_name = self.type_mapper.sanitize_field_name(field_name)
|
|
131
|
+
|
|
132
|
+
# Determine if field is required/nullable
|
|
133
|
+
is_required = field_name in required_fields
|
|
134
|
+
is_nullable = field_schema.nullable or False
|
|
135
|
+
is_repeated = field_schema.type == "array"
|
|
136
|
+
|
|
137
|
+
# Get field type
|
|
138
|
+
if is_repeated:
|
|
139
|
+
# Array field - use items type
|
|
140
|
+
if field_schema.items:
|
|
141
|
+
if field_schema.items.type == "object":
|
|
142
|
+
# Nested object array
|
|
143
|
+
item_type = self.type_mapper.get_message_name(field_name + "Item")
|
|
144
|
+
# Generate the nested message
|
|
145
|
+
self.generate_message(field_schema.items, item_type)
|
|
146
|
+
elif field_schema.items.enum:
|
|
147
|
+
# Enum array - generate the enum definition
|
|
148
|
+
item_type = self.type_mapper.get_message_name(field_name)
|
|
149
|
+
# Generate the enum if not already generated
|
|
150
|
+
if item_type not in self.generated_messages:
|
|
151
|
+
self.generated_messages.add(item_type)
|
|
152
|
+
enum_def = self._generate_enum(field_schema.items, item_type)
|
|
153
|
+
if enum_def:
|
|
154
|
+
self.message_definitions.append(enum_def)
|
|
155
|
+
else:
|
|
156
|
+
# Scalar array
|
|
157
|
+
item_type = self.type_mapper.map_type(
|
|
158
|
+
field_schema.items.type or "string",
|
|
159
|
+
field_schema.items.format,
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
item_type = "string" # Fallback
|
|
163
|
+
field_type = item_type
|
|
164
|
+
elif field_schema.type == "object":
|
|
165
|
+
# Nested object
|
|
166
|
+
field_type = self.type_mapper.get_message_name(field_name)
|
|
167
|
+
elif field_schema.enum:
|
|
168
|
+
# Enum field
|
|
169
|
+
field_type = self.type_mapper.get_message_name(field_name)
|
|
170
|
+
else:
|
|
171
|
+
# Scalar field
|
|
172
|
+
field_type = self.type_mapper.map_type(
|
|
173
|
+
field_schema.type or "string",
|
|
174
|
+
field_schema.format,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Get field label
|
|
178
|
+
label = self.type_mapper.get_field_label(is_required, is_nullable, is_repeated)
|
|
179
|
+
|
|
180
|
+
# Build field definition
|
|
181
|
+
if label:
|
|
182
|
+
return f"{label} {field_type} {proto_field_name} = {field_number};"
|
|
183
|
+
else:
|
|
184
|
+
return f"{field_type} {proto_field_name} = {field_number};"
|
|
185
|
+
|
|
186
|
+
def _generate_enum(
|
|
187
|
+
self, schema: IRSchemaObject, enum_name: str, indent: int = 0
|
|
188
|
+
) -> str:
|
|
189
|
+
"""
|
|
190
|
+
Generate an enum definition.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
schema: Schema with enum values
|
|
194
|
+
enum_name: Enum name
|
|
195
|
+
indent: Indentation level for nested enums
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Enum definition string
|
|
199
|
+
"""
|
|
200
|
+
if not schema.enum:
|
|
201
|
+
return ""
|
|
202
|
+
|
|
203
|
+
indent_str = " " * indent
|
|
204
|
+
lines = [f"{indent_str}enum {enum_name} {{"]
|
|
205
|
+
|
|
206
|
+
# Proto enums must start with 0
|
|
207
|
+
# Add UNKNOWN/UNSPECIFIED as first value if not present
|
|
208
|
+
enum_values = list(schema.enum)
|
|
209
|
+
if not any(
|
|
210
|
+
v.upper() in ("UNKNOWN", "UNSPECIFIED", f"{enum_name}_UNKNOWN")
|
|
211
|
+
for v in enum_values
|
|
212
|
+
):
|
|
213
|
+
lines.append(f"{indent_str} {enum_name.upper()}_UNKNOWN = 0;")
|
|
214
|
+
start_index = 1
|
|
215
|
+
else:
|
|
216
|
+
start_index = 0
|
|
217
|
+
|
|
218
|
+
# Generate enum values
|
|
219
|
+
for idx, value in enumerate(enum_values, start=start_index):
|
|
220
|
+
# Convert to UPPER_SNAKE_CASE
|
|
221
|
+
enum_value_name = (
|
|
222
|
+
str(value).replace("-", "_").replace(" ", "_").replace(".", "_").upper()
|
|
223
|
+
)
|
|
224
|
+
# Add enum name prefix if not already present
|
|
225
|
+
if not enum_value_name.startswith(enum_name.upper()):
|
|
226
|
+
enum_value_name = f"{enum_name.upper()}_{enum_value_name}"
|
|
227
|
+
|
|
228
|
+
lines.append(f"{indent_str} {enum_value_name} = {idx};")
|
|
229
|
+
|
|
230
|
+
lines.append(f"{indent_str}}}")
|
|
231
|
+
|
|
232
|
+
return "\n".join(lines)
|
|
233
|
+
|
|
234
|
+
def generate_all_messages(self, schemas: dict[str, IRSchemaObject]) -> list[str]:
|
|
235
|
+
"""
|
|
236
|
+
Generate all message definitions from a collection of schemas.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
schemas: Dictionary of schema_name -> IRSchemaObject
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
List of proto message definition strings
|
|
243
|
+
"""
|
|
244
|
+
self.generated_messages.clear()
|
|
245
|
+
self.message_definitions.clear()
|
|
246
|
+
|
|
247
|
+
for schema_name, schema in schemas.items():
|
|
248
|
+
message_name = self.type_mapper.get_message_name(schema_name)
|
|
249
|
+
self.generate_message(schema, message_name)
|
|
250
|
+
|
|
251
|
+
return self.message_definitions
|
|
252
|
+
|
|
253
|
+
def get_all_definitions(self) -> str:
|
|
254
|
+
"""
|
|
255
|
+
Get all generated message definitions as a single string.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Combined proto definitions separated by blank lines
|
|
259
|
+
"""
|
|
260
|
+
return "\n\n".join(self.message_definitions)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proto Services Generator - Generates gRPC service definitions from IR operations.
|
|
3
|
+
|
|
4
|
+
Converts IROperationObject instances into proto3 service and rpc definitions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from .type_mapper import ProtoTypeMapper
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from django_cfg.modules.django_client.core.ir.operation import IROperationObject
|
|
15
|
+
from django_cfg.modules.django_client.core.ir.schema import IRSchemaObject
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProtoServicesGenerator:
|
|
19
|
+
"""
|
|
20
|
+
Generates gRPC service definitions from IR operations.
|
|
21
|
+
|
|
22
|
+
Handles:
|
|
23
|
+
- Service grouping by tags
|
|
24
|
+
- RPC method definitions
|
|
25
|
+
- Request/Response message generation
|
|
26
|
+
- Empty responses (google.protobuf.Empty)
|
|
27
|
+
- Stream annotations (if needed in future)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, type_mapper: ProtoTypeMapper, context: IRContext | None = None):
|
|
31
|
+
self.type_mapper = type_mapper
|
|
32
|
+
self.context = context # Need context to resolve schema references
|
|
33
|
+
self.services: dict[str, list[str]] = {} # service_name -> [rpc_definitions]
|
|
34
|
+
self.request_messages: dict[str, str] = {} # Track generated request messages
|
|
35
|
+
|
|
36
|
+
def generate_rpc(self, operation: IROperationObject) -> tuple[str, list[str]]:
|
|
37
|
+
"""
|
|
38
|
+
Generate an RPC definition from an IR operation.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
operation: IR operation object
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Tuple of (service_name, [request_message_defs, response_message_defs, rpc_def])
|
|
45
|
+
"""
|
|
46
|
+
# Determine service name from tags (first tag or "Default")
|
|
47
|
+
service_name = self._get_service_name(operation)
|
|
48
|
+
|
|
49
|
+
# Generate RPC method name
|
|
50
|
+
rpc_name = self._get_rpc_name(operation)
|
|
51
|
+
|
|
52
|
+
# Generate request message
|
|
53
|
+
request_message_name, request_msg_def = self._generate_request_message(
|
|
54
|
+
operation, rpc_name
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Generate response message
|
|
58
|
+
response_message_name, response_msg_def = self._generate_response_message(
|
|
59
|
+
operation, rpc_name
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Generate RPC definition
|
|
63
|
+
rpc_def = f" rpc {rpc_name}({request_message_name}) returns ({response_message_name});"
|
|
64
|
+
|
|
65
|
+
# Add comment if operation has description
|
|
66
|
+
if operation.description:
|
|
67
|
+
# Sanitize description for proto comment
|
|
68
|
+
desc = operation.description.strip().replace("\n", "\n // ")
|
|
69
|
+
rpc_def = f" // {desc}\n{rpc_def}"
|
|
70
|
+
|
|
71
|
+
# Collect all definitions
|
|
72
|
+
definitions = []
|
|
73
|
+
if request_msg_def:
|
|
74
|
+
definitions.append(request_msg_def)
|
|
75
|
+
if response_msg_def:
|
|
76
|
+
definitions.append(response_msg_def)
|
|
77
|
+
definitions.append(rpc_def)
|
|
78
|
+
|
|
79
|
+
return service_name, definitions
|
|
80
|
+
|
|
81
|
+
def _get_service_name(self, operation: IROperationObject) -> str:
|
|
82
|
+
"""Get service name from operation tags."""
|
|
83
|
+
if operation.tags and len(operation.tags) > 0:
|
|
84
|
+
service_name = operation.tags[0]
|
|
85
|
+
else:
|
|
86
|
+
service_name = "Default"
|
|
87
|
+
|
|
88
|
+
# Convert to PascalCase and add "Service" suffix
|
|
89
|
+
return self.type_mapper.get_message_name(service_name) + "Service"
|
|
90
|
+
|
|
91
|
+
def _get_rpc_name(self, operation: IROperationObject) -> str:
|
|
92
|
+
"""Get RPC method name from operation."""
|
|
93
|
+
if operation.operation_id:
|
|
94
|
+
# Use operation_id (already should be camelCase from OpenAPI)
|
|
95
|
+
name = operation.operation_id
|
|
96
|
+
else:
|
|
97
|
+
# Fallback: method + path
|
|
98
|
+
path_parts = [p for p in operation.path.split("/") if p and not p.startswith("{")]
|
|
99
|
+
name = operation.method + "_" + "_".join(path_parts)
|
|
100
|
+
|
|
101
|
+
# Ensure PascalCase for RPC method name
|
|
102
|
+
return self.type_mapper.get_message_name(name)
|
|
103
|
+
|
|
104
|
+
def _generate_request_message(
|
|
105
|
+
self, operation: IROperationObject, rpc_name: str
|
|
106
|
+
) -> tuple[str, str]:
|
|
107
|
+
"""
|
|
108
|
+
Generate request message for an RPC.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
operation: IR operation
|
|
112
|
+
rpc_name: RPC method name
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Tuple of (message_name, message_definition)
|
|
116
|
+
"""
|
|
117
|
+
message_name = f"{rpc_name}Request"
|
|
118
|
+
|
|
119
|
+
# Check if we need a request message at all
|
|
120
|
+
has_patch_body = hasattr(operation, 'patch_request_body') and operation.patch_request_body
|
|
121
|
+
has_params = bool(
|
|
122
|
+
operation.parameters
|
|
123
|
+
or operation.request_body
|
|
124
|
+
or has_patch_body
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if not has_params:
|
|
128
|
+
# No parameters - use empty message or google.protobuf.Empty
|
|
129
|
+
self.type_mapper.imported_types.add("google.protobuf.Empty")
|
|
130
|
+
return "google.protobuf.Empty", ""
|
|
131
|
+
|
|
132
|
+
# Build request message
|
|
133
|
+
lines = [f"message {message_name} {{"]
|
|
134
|
+
field_number = 1
|
|
135
|
+
|
|
136
|
+
# Add path/query parameters
|
|
137
|
+
if operation.parameters:
|
|
138
|
+
for param in operation.parameters:
|
|
139
|
+
if param.location in ("path", "query"):
|
|
140
|
+
field_name = self.type_mapper.sanitize_field_name(param.name)
|
|
141
|
+
field_type = self.type_mapper.map_type(
|
|
142
|
+
param.schema_type,
|
|
143
|
+
None, # IRParameterObject doesn't have format
|
|
144
|
+
)
|
|
145
|
+
is_required = param.required
|
|
146
|
+
is_nullable = False # Parameters don't have nullable in IR
|
|
147
|
+
|
|
148
|
+
label = self.type_mapper.get_field_label(
|
|
149
|
+
is_required, is_nullable, False
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if label:
|
|
153
|
+
lines.append(
|
|
154
|
+
f" {label} {field_type} {field_name} = {field_number};"
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
lines.append(f" {field_type} {field_name} = {field_number};")
|
|
158
|
+
|
|
159
|
+
field_number += 1
|
|
160
|
+
|
|
161
|
+
# Add request body as a single field referencing the schema
|
|
162
|
+
if operation.request_body:
|
|
163
|
+
# Check if this is multipart/form-data (file upload)
|
|
164
|
+
is_multipart = operation.request_body.content_type and "multipart" in operation.request_body.content_type
|
|
165
|
+
|
|
166
|
+
if is_multipart:
|
|
167
|
+
# For file uploads, use bytes type directly
|
|
168
|
+
# Note: In real gRPC, file uploads should use streaming (not implemented here)
|
|
169
|
+
field_name = "file_data"
|
|
170
|
+
field_type = "bytes"
|
|
171
|
+
is_required = operation.request_body.required
|
|
172
|
+
label = self.type_mapper.get_field_label(is_required, False, False)
|
|
173
|
+
|
|
174
|
+
if label:
|
|
175
|
+
lines.append(f" {label} {field_type} {field_name} = {field_number};")
|
|
176
|
+
else:
|
|
177
|
+
lines.append(f" {field_type} {field_name} = {field_number};")
|
|
178
|
+
else:
|
|
179
|
+
# Use schema_name to reference the request body schema
|
|
180
|
+
schema_name = operation.request_body.schema_name
|
|
181
|
+
field_name = "body"
|
|
182
|
+
field_type = self.type_mapper.get_message_name(schema_name)
|
|
183
|
+
|
|
184
|
+
is_required = operation.request_body.required
|
|
185
|
+
label = self.type_mapper.get_field_label(is_required, False, False)
|
|
186
|
+
|
|
187
|
+
if label:
|
|
188
|
+
lines.append(f" {label} {field_type} {field_name} = {field_number};")
|
|
189
|
+
else:
|
|
190
|
+
lines.append(f" {field_type} {field_name} = {field_number};")
|
|
191
|
+
elif has_patch_body:
|
|
192
|
+
# PATCH operations have optional body
|
|
193
|
+
schema_name = operation.patch_request_body.schema_name
|
|
194
|
+
field_name = "body"
|
|
195
|
+
field_type = self.type_mapper.get_message_name(schema_name)
|
|
196
|
+
|
|
197
|
+
# PATCH body is always optional
|
|
198
|
+
label = "optional"
|
|
199
|
+
lines.append(f" {label} {field_type} {field_name} = {field_number};")
|
|
200
|
+
|
|
201
|
+
lines.append("}")
|
|
202
|
+
|
|
203
|
+
return message_name, "\n".join(lines)
|
|
204
|
+
|
|
205
|
+
def _generate_response_message(
|
|
206
|
+
self, operation: IROperationObject, rpc_name: str
|
|
207
|
+
) -> tuple[str, str]:
|
|
208
|
+
"""
|
|
209
|
+
Generate response message for an RPC.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
operation: IR operation
|
|
213
|
+
rpc_name: RPC method name
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Tuple of (message_name, message_definition)
|
|
217
|
+
"""
|
|
218
|
+
message_name = f"{rpc_name}Response"
|
|
219
|
+
|
|
220
|
+
# Get successful response (200, 201, etc.)
|
|
221
|
+
response_schema_name = None
|
|
222
|
+
for status_code, response in operation.responses.items():
|
|
223
|
+
if isinstance(status_code, int) and 200 <= status_code < 300:
|
|
224
|
+
# Found successful response
|
|
225
|
+
if response.schema_name:
|
|
226
|
+
response_schema_name = response.schema_name
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
# No response body - use Empty
|
|
230
|
+
if not response_schema_name:
|
|
231
|
+
self.type_mapper.imported_types.add("google.protobuf.Empty")
|
|
232
|
+
return "google.protobuf.Empty", ""
|
|
233
|
+
|
|
234
|
+
# Build response message - simply reference the schema
|
|
235
|
+
lines = [f"message {message_name} {{"]
|
|
236
|
+
|
|
237
|
+
# Reference the response schema as a single field
|
|
238
|
+
field_type = self.type_mapper.get_message_name(response_schema_name)
|
|
239
|
+
lines.append(f" {field_type} data = 1;")
|
|
240
|
+
|
|
241
|
+
lines.append("}")
|
|
242
|
+
|
|
243
|
+
return message_name, "\n".join(lines)
|
|
244
|
+
|
|
245
|
+
def generate_all_services(
|
|
246
|
+
self, operations: list[IROperationObject]
|
|
247
|
+
) -> dict[str, str]:
|
|
248
|
+
"""
|
|
249
|
+
Generate all service definitions from operations.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
operations: List of IR operations
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Dictionary of service_name -> service_definition
|
|
256
|
+
"""
|
|
257
|
+
self.services.clear()
|
|
258
|
+
self.request_messages.clear()
|
|
259
|
+
|
|
260
|
+
# Group operations by service
|
|
261
|
+
messages_by_service: dict[str, list[str]] = {}
|
|
262
|
+
rpcs_by_service: dict[str, list[str]] = {}
|
|
263
|
+
|
|
264
|
+
for operation in operations:
|
|
265
|
+
service_name, definitions = self.generate_rpc(operation)
|
|
266
|
+
|
|
267
|
+
if service_name not in messages_by_service:
|
|
268
|
+
messages_by_service[service_name] = []
|
|
269
|
+
rpcs_by_service[service_name] = []
|
|
270
|
+
|
|
271
|
+
# Separate messages from RPC definitions
|
|
272
|
+
for definition in definitions:
|
|
273
|
+
if definition.startswith("message "):
|
|
274
|
+
messages_by_service[service_name].append(definition)
|
|
275
|
+
elif definition.strip().startswith("rpc ") or definition.strip().startswith("//"):
|
|
276
|
+
rpcs_by_service[service_name].append(definition)
|
|
277
|
+
|
|
278
|
+
# Build service definitions
|
|
279
|
+
service_definitions = {}
|
|
280
|
+
for service_name in rpcs_by_service:
|
|
281
|
+
lines = []
|
|
282
|
+
|
|
283
|
+
# Add messages first
|
|
284
|
+
if service_name in messages_by_service:
|
|
285
|
+
lines.extend(messages_by_service[service_name])
|
|
286
|
+
lines.append("") # Blank line between messages and service
|
|
287
|
+
|
|
288
|
+
# Add service definition
|
|
289
|
+
lines.append(f"service {service_name} {{")
|
|
290
|
+
lines.extend(rpcs_by_service[service_name])
|
|
291
|
+
lines.append("}")
|
|
292
|
+
|
|
293
|
+
service_definitions[service_name] = "\n".join(lines)
|
|
294
|
+
|
|
295
|
+
return service_definitions
|