Skip to content

Commit 2e667cc

Browse files
akoclaude
andcommitted
feat: ALTER PUBLISHED REST SERVICE (#161)
Adds incremental editing of published REST services so users can change properties or swap individual resources without dropping and recreating the whole service via CREATE OR REPLACE. Three action forms are supported and may be combined in a single statement: - SET Path|Version|ServiceName = '...' [, ...] - ADD RESOURCE 'name' { ... } (reuses CREATE syntax) - DROP RESOURCE 'name' Mirrors the existing ALTER ODATA SERVICE pattern: the executor mutates the in-memory model, then a new UpdatePublishedRestService writer re-serializes the document via updateUnit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 635d737 commit 2e667cc

14 files changed

Lines changed: 10250 additions & 9406 deletions

File tree

cmd/mxcli/help_topics/rest.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ CREATE / DROP PUBLISHED REST SERVICE
3838
Properties: Path (required), Version, ServiceName, Folder
3939
HTTP methods: GET, POST, PUT, DELETE, PATCH
4040

41+
ALTER PUBLISHED REST SERVICE
42+
----------------------------
43+
44+
-- Change properties (Path, Version, ServiceName)
45+
ALTER PUBLISHED REST SERVICE Module.MyAPI SET Version = '2.0.0';
46+
ALTER PUBLISHED REST SERVICE Module.MyAPI
47+
SET Path = 'rest/api/v2', ServiceName = 'My API v2';
48+
49+
-- Add a new resource block
50+
ALTER PUBLISHED REST SERVICE Module.MyAPI ADD RESOURCE 'orders/{id}/items' {
51+
GET '' MICROFLOW Module.GetOrderItems;
52+
};
53+
54+
-- Drop a resource by name
55+
ALTER PUBLISHED REST SERVICE Module.MyAPI DROP RESOURCE 'legacy';
56+
4157
GRANT / REVOKE ACCESS
4258
---------------------
4359

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ CREATE OR REPLACE NAVIGATION Responsive
444444
| Describe service | `DESCRIBE PUBLISHED REST SERVICE Module.Name;` | Re-executable CREATE statement |
445445
| Create service | See below | |
446446
| Create or replace | `CREATE OR REPLACE PUBLISHED REST SERVICE ...` | Replaces existing service |
447+
| Alter service | `ALTER PUBLISHED REST SERVICE Module.Name SET Path = '...', Version = '...';` | SET supports Path, Version, ServiceName |
448+
| Add resource | `ALTER PUBLISHED REST SERVICE Module.Name ADD RESOURCE 'name' { ... };` | Operation block uses CREATE syntax |
449+
| Drop resource | `ALTER PUBLISHED REST SERVICE Module.Name DROP RESOURCE 'name';` | |
447450
| Drop service | `DROP PUBLISHED REST SERVICE Module.Name;` | |
448451
| Grant access | `GRANT ACCESS ON PUBLISHED REST SERVICE Module.Name TO Module.Role, ...;` | Adds module roles to AllowedRoles |
449452
| Revoke access | `REVOKE ACCESS ON PUBLISHED REST SERVICE Module.Name FROM Module.Role, ...;` | |

mdl-examples/doctype-tests/22-published-rest-service-examples.mdl

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,25 @@ REVOKE ACCESS ON PUBLISHED REST SERVICE PrsTest.OrderAPI FROM PrsTest.User;
140140
DESCRIBE PUBLISHED REST SERVICE PrsTest.OrderAPI;
141141

142142
-- ############################################################################
143-
-- PART 7: DROP
143+
-- PART 7: ALTER
144+
-- ############################################################################
145+
--
146+
-- Modify properties or add/remove resources without recreating the service.
147+
148+
ALTER PUBLISHED REST SERVICE PrsTest.OrderAPI SET Version = '1.1.0';
149+
ALTER PUBLISHED REST SERVICE PrsTest.OrderAPI
150+
SET Path = 'rest/orders/v1', ServiceName = 'Order API';
151+
152+
ALTER PUBLISHED REST SERVICE PrsTest.OrderAPI ADD RESOURCE 'shipments' {
153+
GET '' MICROFLOW PrsTest.PRS_GetAllOrders;
154+
};
155+
156+
DESCRIBE PUBLISHED REST SERVICE PrsTest.OrderAPI;
157+
158+
ALTER PUBLISHED REST SERVICE PrsTest.OrderAPI DROP RESOURCE 'shipments';
159+
160+
-- ############################################################################
161+
-- PART 8: DROP
144162
-- ############################################################################
145163

146164
DROP PUBLISHED REST SERVICE PrsTest.OrderAPI;

mdl/ast/ast_rest.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,42 @@ type DropPublishedRestServiceStmt struct {
103103
}
104104

105105
func (s *DropPublishedRestServiceStmt) isStatement() {}
106+
107+
// AlterPublishedRestServiceStmt represents:
108+
//
109+
// ALTER PUBLISHED REST SERVICE Module.Name <action>+
110+
//
111+
// where each action is one of: SET key = 'value' [, ...], ADD RESOURCE
112+
// 'name' { ... }, or DROP RESOURCE 'name'.
113+
type AlterPublishedRestServiceStmt struct {
114+
Name QualifiedName
115+
Actions []PublishedRestAlterAction
116+
}
117+
118+
func (s *AlterPublishedRestServiceStmt) isStatement() {}
119+
120+
// PublishedRestAlterAction is one of the alter operations.
121+
type PublishedRestAlterAction interface {
122+
isPublishedRestAlterAction()
123+
}
124+
125+
// PublishedRestSetAction represents: SET key = 'value' [, ...]
126+
type PublishedRestSetAction struct {
127+
Changes map[string]string
128+
}
129+
130+
func (a *PublishedRestSetAction) isPublishedRestAlterAction() {}
131+
132+
// PublishedRestAddResourceAction represents: ADD RESOURCE 'name' { ops... }
133+
type PublishedRestAddResourceAction struct {
134+
Resource *PublishedRestResourceDef
135+
}
136+
137+
func (a *PublishedRestAddResourceAction) isPublishedRestAlterAction() {}
138+
139+
// PublishedRestDropResourceAction represents: DROP RESOURCE 'name'
140+
type PublishedRestDropResourceAction struct {
141+
Name string
142+
}
143+
144+
func (a *PublishedRestDropResourceAction) isPublishedRestAlterAction() {}

mdl/executor/cmd_published_rest.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,83 @@ func (e *Executor) execDropPublishedRestService(s *ast.DropPublishedRestServiceS
269269

270270
return fmt.Errorf("published REST service %s.%s not found", s.Name.Module, s.Name.Name)
271271
}
272+
273+
// astResourceDefToModel converts an AST PublishedRestResourceDef to the
274+
// runtime model type used by the writer.
275+
func astResourceDefToModel(def *ast.PublishedRestResourceDef) *model.PublishedRestResource {
276+
resource := &model.PublishedRestResource{Name: def.Name}
277+
for _, opDef := range def.Operations {
278+
resource.Operations = append(resource.Operations, &model.PublishedRestOperation{
279+
HTTPMethod: opDef.HTTPMethod,
280+
Path: opDef.Path,
281+
Microflow: opDef.Microflow.String(),
282+
Deprecated: opDef.Deprecated,
283+
})
284+
}
285+
return resource
286+
}
287+
288+
// execAlterPublishedRestService applies SET / ADD RESOURCE / DROP RESOURCE
289+
// actions to an existing published REST service.
290+
func (e *Executor) execAlterPublishedRestService(s *ast.AlterPublishedRestServiceStmt) error {
291+
if e.writer == nil {
292+
return fmt.Errorf("not connected to a project in write mode")
293+
}
294+
295+
svc, err := e.findPublishedRestService(s.Name.Module, s.Name.Name)
296+
if err != nil {
297+
return fmt.Errorf("published REST service %s.%s not found", s.Name.Module, s.Name.Name)
298+
}
299+
300+
for _, action := range s.Actions {
301+
switch a := action.(type) {
302+
case *ast.PublishedRestSetAction:
303+
for key, val := range a.Changes {
304+
switch strings.ToLower(key) {
305+
case "path":
306+
svc.Path = val
307+
case "version":
308+
svc.Version = val
309+
case "servicename":
310+
svc.ServiceName = val
311+
default:
312+
return fmt.Errorf("unknown published REST service property: %s (allowed: Path, Version, ServiceName)", key)
313+
}
314+
}
315+
316+
case *ast.PublishedRestAddResourceAction:
317+
// Reject duplicate resource names
318+
for _, existing := range svc.Resources {
319+
if existing.Name == a.Resource.Name {
320+
return fmt.Errorf("resource '%s' already exists on %s.%s", a.Resource.Name, s.Name.Module, s.Name.Name)
321+
}
322+
}
323+
svc.Resources = append(svc.Resources, astResourceDefToModel(a.Resource))
324+
325+
case *ast.PublishedRestDropResourceAction:
326+
idx := -1
327+
for i, existing := range svc.Resources {
328+
if existing.Name == a.Name {
329+
idx = i
330+
break
331+
}
332+
}
333+
if idx == -1 {
334+
return fmt.Errorf("resource '%s' not found on %s.%s", a.Name, s.Name.Module, s.Name.Name)
335+
}
336+
svc.Resources = append(svc.Resources[:idx], svc.Resources[idx+1:]...)
337+
338+
default:
339+
return fmt.Errorf("unsupported alter action: %T", action)
340+
}
341+
}
342+
343+
if err := e.writer.UpdatePublishedRestService(svc); err != nil {
344+
return fmt.Errorf("failed to alter published REST service: %w", err)
345+
}
346+
347+
if !e.quiet {
348+
fmt.Fprintf(e.output, "Altered published REST service %s.%s\n", s.Name.Module, s.Name.Name)
349+
}
350+
return nil
351+
}

mdl/executor/executor_dispatch.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ func (e *Executor) executeInner(stmt ast.Statement) error {
200200
return e.execCreatePublishedRestService(s)
201201
case *ast.DropPublishedRestServiceStmt:
202202
return e.execDropPublishedRestService(s)
203+
case *ast.AlterPublishedRestServiceStmt:
204+
return e.execAlterPublishedRestService(s)
203205
case *ast.CreateExternalEntityStmt:
204206
return e.execCreateExternalEntity(s)
205207
case *ast.CreateExternalEntitiesStmt:

mdl/grammar/MDLParser.g4

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ alterStatement
119119
| ALTER PAGE qualifiedName LBRACE alterPageOperation+ RBRACE
120120
| ALTER SNIPPET qualifiedName LBRACE alterPageOperation+ RBRACE
121121
| ALTER WORKFLOW qualifiedName alterWorkflowAction+ SEMICOLON?
122+
| ALTER PUBLISHED REST SERVICE qualifiedName alterPublishedRestServiceAction (COMMA? alterPublishedRestServiceAction)*
123+
;
124+
125+
alterPublishedRestServiceAction
126+
: SET publishedRestAlterAssignment (COMMA publishedRestAlterAssignment)*
127+
| ADD publishedRestResource
128+
| DROP RESOURCE STRING_LITERAL
129+
;
130+
131+
publishedRestAlterAssignment
132+
: identifierOrKeyword EQUALS STRING_LITERAL
122133
;
123134

124135
/**

mdl/grammar/parser/MDLParser.interp

Lines changed: 3 additions & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)