github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/cmd/swagger/commands/mixin.go (about)

     1  package commands
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"log"
     7  	"os"
     8  
     9  	"github.com/go-openapi/analysis"
    10  	"github.com/go-openapi/loads"
    11  	"github.com/go-openapi/spec"
    12  	flags "github.com/jessevdk/go-flags"
    13  
    14  	"github.com/thetreep/go-swagger/generator"
    15  )
    16  
    17  const (
    18  	// Output messages
    19  	nothingToDo                           = "nothing to do. Need some swagger files to merge.\nUSAGE: swagger mixin [-c <expected#Collisions>] <primary-swagger-file> <mixin-swagger-file...>"
    20  	ignoreConflictsAndCollisionsSpecified = "both the flags ignore conflicts and collisions were specified. These have conflicting meaning so please only specify one"
    21  )
    22  
    23  // MixinSpec holds command line flag definitions specific to the mixin
    24  // command. The flags are defined using struct field tags with the
    25  // "github.com/jessevdk/go-flags" format.
    26  type MixinSpec struct {
    27  	ExpectedCollisionCount uint           `short:"c" description:"expected # of rejected mixin paths, defs, etc due to existing key. Non-zero exit if does not match actual."`
    28  	Compact                bool           `long:"compact" description:"applies to JSON formatted specs. When present, doesn't prettify the json"`
    29  	Output                 flags.Filename `long:"output" short:"o" description:"the file to write to"`
    30  	KeepSpecOrder          bool           `long:"keep-spec-order" description:"Keep schema properties order identical to spec file"`
    31  	Format                 string         `long:"format" description:"the format for the spec document" default:"json" choice:"yaml" choice:"json"`
    32  	IgnoreConflicts        bool           `long:"ignore-conflicts" description:"Ignore conflict"`
    33  }
    34  
    35  // Execute runs the mixin command which merges Swagger 2.0 specs into
    36  // one spec
    37  //
    38  // Use cases include adding independently versioned metadata APIs to
    39  // application APIs for microservices.
    40  //
    41  // Typically, multiple APIs to the same service instance is not a
    42  // problem for client generation as you can create more than one
    43  // client to the service from the same calling process (one for each
    44  // API).  However, merging clients can improve clarity of client code
    45  // by having a single client to given service vs several.
    46  //
    47  // Server skeleton generation, ie generating the model & marshaling
    48  // code, http server instance etc. from Swagger, becomes easier with a
    49  // merged spec for some tools & target-languages.  Server code
    50  // generation tools that natively support hosting multiple specs in
    51  // one server process will not need this tool.
    52  func (c *MixinSpec) Execute(args []string) error {
    53  
    54  	if len(args) < 2 {
    55  		return errors.New(nothingToDo)
    56  	}
    57  	if c.IgnoreConflicts && c.ExpectedCollisionCount != 0 {
    58  		return errors.New(ignoreConflictsAndCollisionsSpecified)
    59  	}
    60  
    61  	log.Printf("args[0] = %v\n", args[0])
    62  	log.Printf("args[1:] = %v\n", args[1:])
    63  	collisions, err := c.MixinFiles(args[0], args[1:], os.Stdout)
    64  
    65  	for _, warn := range collisions {
    66  		log.Println(warn)
    67  	}
    68  
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	if c.IgnoreConflicts {
    74  		return nil
    75  	}
    76  	if len(collisions) != int(c.ExpectedCollisionCount) {
    77  		if len(collisions) != 0 {
    78  			// use bash $? to get actual # collisions
    79  			// (but has to be non-zero)
    80  			os.Exit(len(collisions))
    81  		}
    82  		os.Exit(254)
    83  	}
    84  	return nil
    85  }
    86  
    87  // MixinFiles is a convenience function for Mixin that reads the given
    88  // swagger files, adds the mixins to primary, calls
    89  // FixEmptyResponseDescriptions on the primary, and writes the primary
    90  // with mixins to the given writer in JSON.  Returns the warning
    91  // messages for collisions that occurred during mixin process and any
    92  // error.
    93  func (c *MixinSpec) MixinFiles(primaryFile string, mixinFiles []string, w io.Writer) ([]string, error) {
    94  
    95  	primaryDoc, err := loads.Spec(primaryFile)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	primary := primaryDoc.Spec()
   100  
   101  	var mixins []*spec.Swagger
   102  	for _, mixinFile := range mixinFiles {
   103  		if c.KeepSpecOrder {
   104  			mixinFile = generator.WithAutoXOrder(mixinFile)
   105  		}
   106  		mixin, lerr := loads.Spec(mixinFile)
   107  		if lerr != nil {
   108  			return nil, lerr
   109  		}
   110  		mixins = append(mixins, mixin.Spec())
   111  	}
   112  
   113  	collisions := analysis.Mixin(primary, mixins...)
   114  	analysis.FixEmptyResponseDescriptions(primary)
   115  
   116  	return collisions, writeToFile(primary, !c.Compact, c.Format, string(c.Output))
   117  }