@@ -467,3 +467,224 @@ func edmToMendixType(p *mpr.EdmProperty) string {
467467 return "String(200)"
468468 }
469469}
470+
471+ // ============================================================================
472+ // AsyncAPI Contract Commands
473+ // ============================================================================
474+
475+ // showContractChannels handles SHOW CONTRACT CHANNELS FROM Module.Service.
476+ func (e * Executor ) showContractChannels (name * ast.QualifiedName ) error {
477+ if name == nil {
478+ return fmt .Errorf ("service name required: SHOW CONTRACT CHANNELS FROM Module.Service" )
479+ }
480+
481+ doc , svcQN , err := e .parseAsyncAPIContract (* name )
482+ if err != nil {
483+ return err
484+ }
485+
486+ if len (doc .Channels ) == 0 {
487+ fmt .Fprintf (e .output , "No channels found in contract for %s.\n " , svcQN )
488+ return nil
489+ }
490+
491+ type row struct {
492+ channel string
493+ operation string
494+ opID string
495+ message string
496+ }
497+
498+ var rows []row
499+ chWidth := len ("Channel" )
500+ opWidth := len ("Operation" )
501+ opIDWidth := len ("OperationID" )
502+ msgWidth := len ("Message" )
503+
504+ for _ , ch := range doc .Channels {
505+ rows = append (rows , row {ch .Name , ch .OperationType , ch .OperationID , ch .MessageRef })
506+ if len (ch .Name ) > chWidth {
507+ chWidth = len (ch .Name )
508+ }
509+ if len (ch .OperationType ) > opWidth {
510+ opWidth = len (ch .OperationType )
511+ }
512+ if len (ch .OperationID ) > opIDWidth {
513+ opIDWidth = len (ch .OperationID )
514+ }
515+ if len (ch .MessageRef ) > msgWidth {
516+ msgWidth = len (ch .MessageRef )
517+ }
518+ }
519+
520+ fmt .Fprintf (e .output , "| %-*s | %-*s | %-*s | %-*s |\n " ,
521+ chWidth , "Channel" , opWidth , "Operation" , opIDWidth , "OperationID" , msgWidth , "Message" )
522+ fmt .Fprintf (e .output , "|-%s-|-%s-|-%s-|-%s-|\n " ,
523+ strings .Repeat ("-" , chWidth ), strings .Repeat ("-" , opWidth ),
524+ strings .Repeat ("-" , opIDWidth ), strings .Repeat ("-" , msgWidth ))
525+ for _ , r := range rows {
526+ fmt .Fprintf (e .output , "| %-*s | %-*s | %-*s | %-*s |\n " ,
527+ chWidth , r .channel , opWidth , r .operation , opIDWidth , r .opID , msgWidth , r .message )
528+ }
529+ fmt .Fprintf (e .output , "\n (%d channels in %s contract)\n " , len (rows ), svcQN )
530+
531+ return nil
532+ }
533+
534+ // showContractMessages handles SHOW CONTRACT MESSAGES FROM Module.Service.
535+ func (e * Executor ) showContractMessages (name * ast.QualifiedName ) error {
536+ if name == nil {
537+ return fmt .Errorf ("service name required: SHOW CONTRACT MESSAGES FROM Module.Service" )
538+ }
539+
540+ doc , svcQN , err := e .parseAsyncAPIContract (* name )
541+ if err != nil {
542+ return err
543+ }
544+
545+ if len (doc .Messages ) == 0 {
546+ fmt .Fprintf (e .output , "No messages found in contract for %s.\n " , svcQN )
547+ return nil
548+ }
549+
550+ type row struct {
551+ name string
552+ title string
553+ contentType string
554+ props int
555+ }
556+
557+ var rows []row
558+ nameWidth := len ("Message" )
559+ titleWidth := len ("Title" )
560+ ctWidth := len ("ContentType" )
561+
562+ for _ , msg := range doc .Messages {
563+ rows = append (rows , row {msg .Name , msg .Title , msg .ContentType , len (msg .Properties )})
564+ if len (msg .Name ) > nameWidth {
565+ nameWidth = len (msg .Name )
566+ }
567+ if len (msg .Title ) > titleWidth {
568+ titleWidth = len (msg .Title )
569+ }
570+ if len (msg .ContentType ) > ctWidth {
571+ ctWidth = len (msg .ContentType )
572+ }
573+ }
574+
575+ sort .Slice (rows , func (i , j int ) bool {
576+ return strings .ToLower (rows [i ].name ) < strings .ToLower (rows [j ].name )
577+ })
578+
579+ propsWidth := len ("Props" )
580+
581+ fmt .Fprintf (e .output , "| %-*s | %-*s | %-*s | %-*s |\n " ,
582+ nameWidth , "Message" , titleWidth , "Title" , ctWidth , "ContentType" , propsWidth , "Props" )
583+ fmt .Fprintf (e .output , "|-%s-|-%s-|-%s-|-%s-|\n " ,
584+ strings .Repeat ("-" , nameWidth ), strings .Repeat ("-" , titleWidth ),
585+ strings .Repeat ("-" , ctWidth ), strings .Repeat ("-" , propsWidth ))
586+ for _ , r := range rows {
587+ fmt .Fprintf (e .output , "| %-*s | %-*s | %-*s | %-*d |\n " ,
588+ nameWidth , r .name , titleWidth , r .title , ctWidth , r .contentType , propsWidth , r .props )
589+ }
590+ fmt .Fprintf (e .output , "\n (%d messages in %s contract)\n " , len (rows ), svcQN )
591+
592+ return nil
593+ }
594+
595+ // describeContractMessage handles DESCRIBE CONTRACT MESSAGE Module.Service.MessageName.
596+ func (e * Executor ) describeContractMessage (name ast.QualifiedName ) error {
597+ svcName , msgName , err := splitContractRef (name )
598+ if err != nil {
599+ return err
600+ }
601+
602+ doc , svcQN , err := e .parseAsyncAPIContract (svcName )
603+ if err != nil {
604+ return err
605+ }
606+
607+ msg := doc .FindMessage (msgName )
608+ if msg == nil {
609+ return fmt .Errorf ("message %q not found in contract for %s" , msgName , svcQN )
610+ }
611+
612+ fmt .Fprintf (e .output , "%s\n " , msg .Name )
613+ if msg .Title != "" {
614+ fmt .Fprintf (e .output , " Title: %s\n " , msg .Title )
615+ }
616+ if msg .Description != "" {
617+ fmt .Fprintf (e .output , " Description: %s\n " , msg .Description )
618+ }
619+ if msg .ContentType != "" {
620+ fmt .Fprintf (e .output , " ContentType: %s\n " , msg .ContentType )
621+ }
622+
623+ if len (msg .Properties ) > 0 {
624+ fmt .Fprintln (e .output )
625+ nameWidth := len ("Property" )
626+ typeWidth := len ("Type" )
627+ for _ , p := range msg .Properties {
628+ if len (p .Name ) > nameWidth {
629+ nameWidth = len (p .Name )
630+ }
631+ t := asyncTypeString (p )
632+ if len (t ) > typeWidth {
633+ typeWidth = len (t )
634+ }
635+ }
636+
637+ fmt .Fprintf (e .output , " %-*s %-*s\n " , nameWidth , "Property" , typeWidth , "Type" )
638+ fmt .Fprintf (e .output , " %s %s\n " , strings .Repeat ("-" , nameWidth ), strings .Repeat ("-" , typeWidth ))
639+ for _ , p := range msg .Properties {
640+ fmt .Fprintf (e .output , " %-*s %-*s\n " , nameWidth , p .Name , typeWidth , asyncTypeString (p ))
641+ }
642+ }
643+
644+ return nil
645+ }
646+
647+ // parseAsyncAPIContract finds a business event service by name and parses its cached AsyncAPI document.
648+ func (e * Executor ) parseAsyncAPIContract (name ast.QualifiedName ) (* mpr.AsyncAPIDocument , string , error ) {
649+ services , err := e .reader .ListBusinessEventServices ()
650+ if err != nil {
651+ return nil , "" , fmt .Errorf ("failed to list business event services: %w" , err )
652+ }
653+
654+ h , err := e .getHierarchy ()
655+ if err != nil {
656+ return nil , "" , fmt .Errorf ("failed to build hierarchy: %w" , err )
657+ }
658+
659+ for _ , svc := range services {
660+ modID := h .FindModuleID (svc .ContainerID )
661+ modName := h .GetModuleName (modID )
662+
663+ if ! strings .EqualFold (modName , name .Module ) || ! strings .EqualFold (svc .Name , name .Name ) {
664+ continue
665+ }
666+
667+ svcQN := modName + "." + svc .Name
668+
669+ if svc .Document == "" {
670+ return nil , svcQN , fmt .Errorf ("no cached AsyncAPI contract for %s. This service has no Document field (it may be a publisher, not a consumer)" , svcQN )
671+ }
672+
673+ doc , err := mpr .ParseAsyncAPI (svc .Document )
674+ if err != nil {
675+ return nil , svcQN , fmt .Errorf ("failed to parse AsyncAPI contract for %s: %w" , svcQN , err )
676+ }
677+
678+ return doc , svcQN , nil
679+ }
680+
681+ return nil , "" , fmt .Errorf ("business event service not found: %s.%s" , name .Module , name .Name )
682+ }
683+
684+ // asyncTypeString formats an AsyncAPI property type for display.
685+ func asyncTypeString (p * mpr.AsyncAPIProperty ) string {
686+ if p .Format != "" {
687+ return p .Type + " (" + p .Format + ")"
688+ }
689+ return p .Type
690+ }
0 commit comments