Skip to content

Commit 13a77b8

Browse files
akoclaude
andcommitted
fix: add JSON output for SHOW STRUCTURE and SHOW SECURITY MATRIX
Both commands wrote text output directly via fmt.Fprintf, bypassing the format dispatcher. Now they detect FormatJSON and emit structured TableResult output instead. - SHOW STRUCTURE --json: one row per module with element type counts - SHOW SECURITY MATRIX --json: one row per access rule across entities, microflows, pages, and workflows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 97e269e commit 13a77b8

File tree

2 files changed

+198
-1
lines changed

2 files changed

+198
-1
lines changed

mdl/executor/cmd_security.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ func (e *Executor) showAccessOnWorkflow(name *ast.QualifiedName) error {
347347

348348
// showSecurityMatrix handles SHOW SECURITY MATRIX [IN module].
349349
func (e *Executor) showSecurityMatrix(moduleName string) error {
350+
if e.format == FormatJSON {
351+
return e.showSecurityMatrixJSON(moduleName)
352+
}
353+
350354
h, err := e.getHierarchy()
351355
if err != nil {
352356
return fmt.Errorf("failed to build hierarchy: %w", err)
@@ -566,6 +570,145 @@ func (e *Executor) showSecurityMatrix(moduleName string) error {
566570
return nil
567571
}
568572

573+
// showSecurityMatrixJSON emits the security matrix as a JSON table
574+
// with one row per access rule across entities, microflows, pages, and workflows.
575+
func (e *Executor) showSecurityMatrixJSON(moduleName string) error {
576+
h, err := e.getHierarchy()
577+
if err != nil {
578+
return fmt.Errorf("failed to build hierarchy: %w", err)
579+
}
580+
581+
tr := &TableResult{
582+
Columns: []string{"ObjectType", "QualifiedName", "Roles", "Rights"},
583+
}
584+
585+
// Entities
586+
dms, _ := e.reader.ListDomainModels()
587+
for _, dm := range dms {
588+
modID := h.FindModuleID(dm.ContainerID)
589+
modName := h.GetModuleName(modID)
590+
if moduleName != "" && modName != moduleName {
591+
continue
592+
}
593+
for _, entity := range dm.Entities {
594+
for _, rule := range entity.AccessRules {
595+
var roleStrs []string
596+
for _, rn := range rule.ModuleRoleNames {
597+
roleStrs = append(roleStrs, rn)
598+
}
599+
if len(roleStrs) == 0 {
600+
for _, rid := range rule.ModuleRoles {
601+
roleStrs = append(roleStrs, string(rid))
602+
}
603+
}
604+
605+
var rights []string
606+
if rule.AllowCreate {
607+
rights = append(rights, "C")
608+
}
609+
rr := rule.DefaultMemberAccessRights == domainmodel.MemberAccessRightsReadOnly ||
610+
rule.DefaultMemberAccessRights == domainmodel.MemberAccessRightsReadWrite
611+
rw := rule.DefaultMemberAccessRights == domainmodel.MemberAccessRightsReadWrite
612+
for _, ma := range rule.MemberAccesses {
613+
if ma.AccessRights == domainmodel.MemberAccessRightsReadOnly || ma.AccessRights == domainmodel.MemberAccessRightsReadWrite {
614+
rr = true
615+
}
616+
if ma.AccessRights == domainmodel.MemberAccessRightsReadWrite {
617+
rw = true
618+
}
619+
}
620+
if rr {
621+
rights = append(rights, "R")
622+
}
623+
if rw {
624+
rights = append(rights, "W")
625+
}
626+
if rule.AllowDelete {
627+
rights = append(rights, "D")
628+
}
629+
630+
tr.Rows = append(tr.Rows, []any{
631+
"Entity",
632+
modName + "." + entity.Name,
633+
strings.Join(roleStrs, ", "),
634+
strings.Join(rights, ""),
635+
})
636+
}
637+
}
638+
}
639+
640+
// Microflows
641+
mfs, _ := e.reader.ListMicroflows()
642+
for _, mf := range mfs {
643+
if len(mf.AllowedModuleRoles) == 0 {
644+
continue
645+
}
646+
modID := h.FindModuleID(mf.ContainerID)
647+
modName := h.GetModuleName(modID)
648+
if moduleName != "" && modName != moduleName {
649+
continue
650+
}
651+
var roleStrs []string
652+
for _, r := range mf.AllowedModuleRoles {
653+
roleStrs = append(roleStrs, string(r))
654+
}
655+
tr.Rows = append(tr.Rows, []any{
656+
"Microflow",
657+
modName + "." + mf.Name,
658+
strings.Join(roleStrs, ", "),
659+
"X",
660+
})
661+
}
662+
663+
// Pages
664+
pages, _ := e.reader.ListPages()
665+
for _, pg := range pages {
666+
if len(pg.AllowedRoles) == 0 {
667+
continue
668+
}
669+
modID := h.FindModuleID(pg.ContainerID)
670+
modName := h.GetModuleName(modID)
671+
if moduleName != "" && modName != moduleName {
672+
continue
673+
}
674+
var roleStrs []string
675+
for _, r := range pg.AllowedRoles {
676+
roleStrs = append(roleStrs, string(r))
677+
}
678+
tr.Rows = append(tr.Rows, []any{
679+
"Page",
680+
modName + "." + pg.Name,
681+
strings.Join(roleStrs, ", "),
682+
"X",
683+
})
684+
}
685+
686+
// Workflows
687+
wfs, _ := e.reader.ListWorkflows()
688+
for _, wf := range wfs {
689+
if len(wf.AllowedModuleRoles) == 0 {
690+
continue
691+
}
692+
modID := h.FindModuleID(wf.ContainerID)
693+
modName := h.GetModuleName(modID)
694+
if moduleName != "" && modName != moduleName {
695+
continue
696+
}
697+
var roleStrs []string
698+
for _, r := range wf.AllowedModuleRoles {
699+
roleStrs = append(roleStrs, string(r))
700+
}
701+
tr.Rows = append(tr.Rows, []any{
702+
"Workflow",
703+
modName + "." + wf.Name,
704+
strings.Join(roleStrs, ", "),
705+
"X",
706+
})
707+
}
708+
709+
return e.writeResult(tr)
710+
}
711+
569712
// describeModuleRole handles DESCRIBE MODULE ROLE Module.RoleName.
570713
func (e *Executor) describeModuleRole(name ast.QualifiedName) error {
571714
h, err := e.getHierarchy()

mdl/executor/cmd_structure.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,19 @@ func (e *Executor) execShowStructure(s *ast.ShowStmt) error {
3535
}
3636

3737
if len(modules) == 0 {
38-
fmt.Fprintln(e.output, "(no modules found)")
38+
if e.format == FormatJSON {
39+
fmt.Fprintln(e.output, "[]")
40+
} else {
41+
fmt.Fprintln(e.output, "(no modules found)")
42+
}
3943
return nil
4044
}
4145

46+
// JSON mode: emit structured table
47+
if e.format == FormatJSON {
48+
return e.structureDepth1JSON(modules)
49+
}
50+
4251
switch depth {
4352
case 1:
4453
return e.structureDepth1(modules)
@@ -51,6 +60,51 @@ func (e *Executor) execShowStructure(s *ast.ShowStmt) error {
5160
}
5261
}
5362

63+
// structureDepth1JSON emits structure as a JSON table with one row per module
64+
// and columns for each element type count.
65+
func (e *Executor) structureDepth1JSON(modules []structureModule) error {
66+
entityCounts := e.queryCountByModule("entities")
67+
mfCounts := e.queryCountByModule("microflows WHERE MicroflowType = 'MICROFLOW'")
68+
nfCounts := e.queryCountByModule("microflows WHERE MicroflowType = 'NANOFLOW'")
69+
pageCounts := e.queryCountByModule("pages")
70+
enumCounts := e.queryCountByModule("enumerations")
71+
snippetCounts := e.queryCountByModule("snippets")
72+
jaCounts := e.queryCountByModule("java_actions")
73+
wfCounts := e.queryCountByModule("workflows")
74+
odataClientCounts := e.queryCountByModule("odata_clients")
75+
odataServiceCounts := e.queryCountByModule("odata_services")
76+
beServiceCounts := e.queryCountByModule("business_event_services")
77+
constantCounts := e.countByModuleFromReader("constants")
78+
scheduledEventCounts := e.countByModuleFromReader("scheduled_events")
79+
80+
tr := &TableResult{
81+
Columns: []string{
82+
"Module", "Entities", "Enumerations", "Microflows", "Nanoflows",
83+
"Workflows", "Pages", "Snippets", "JavaActions", "Constants",
84+
"ScheduledEvents", "ODataClients", "ODataServices", "BusinessEventServices",
85+
},
86+
}
87+
for _, m := range modules {
88+
tr.Rows = append(tr.Rows, []any{
89+
m.Name,
90+
entityCounts[m.Name],
91+
enumCounts[m.Name],
92+
mfCounts[m.Name],
93+
nfCounts[m.Name],
94+
wfCounts[m.Name],
95+
pageCounts[m.Name],
96+
snippetCounts[m.Name],
97+
jaCounts[m.Name],
98+
constantCounts[m.Name],
99+
scheduledEventCounts[m.Name],
100+
odataClientCounts[m.Name],
101+
odataServiceCounts[m.Name],
102+
beServiceCounts[m.Name],
103+
})
104+
}
105+
return e.writeResult(tr)
106+
}
107+
54108
// structureModule holds module info for structure output.
55109
type structureModule struct {
56110
Name string

0 commit comments

Comments
 (0)