github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/goagen/gen_app/encoding.go (about)

     1  package genapp
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/goadesign/goa/design"
     8  	"github.com/goadesign/goa/goagen/codegen"
     9  )
    10  
    11  // BuildEncoders builds the template data needed to render the given encoding definitions.
    12  // This extra map is needed to handle the case where a single encoding definition maps to multiple
    13  // encoding packages. The data is indexed by mime type.
    14  func BuildEncoders(info []*design.EncodingDefinition, encoder bool) ([]*EncoderTemplateData, error) {
    15  	if len(info) == 0 {
    16  		return nil, nil
    17  	}
    18  	// knownStdPackages lists the stdlib packages known by BuildEncoders
    19  	var knownStdPackages = map[string]string{
    20  		"encoding/json": "json",
    21  		"encoding/xml":  "xml",
    22  		"encoding/gob":  "gob",
    23  	}
    24  	encs := normalizeEncodingDefinitions(info)
    25  	data := make([]*EncoderTemplateData, len(encs))
    26  	defaultMediaType := info[0].MIMETypes[0]
    27  	for i, enc := range encs {
    28  		var pkgName string
    29  		if name, ok := knownStdPackages[enc.PackagePath]; ok {
    30  			pkgName = name
    31  		} else {
    32  			srcPath, err := codegen.PackageSourcePath(enc.PackagePath)
    33  			if err != nil {
    34  				return nil, fmt.Errorf("failed to locate package source of %s (%s)",
    35  					enc.PackagePath, err)
    36  			}
    37  			pkgName, err = codegen.PackageName(srcPath)
    38  			if err != nil {
    39  				return nil, fmt.Errorf("failed to load package %s (%s)",
    40  					enc.PackagePath, err)
    41  			}
    42  		}
    43  		isDefault := false
    44  		for _, m := range enc.MIMETypes {
    45  			if m == defaultMediaType {
    46  				isDefault = true
    47  			}
    48  		}
    49  		d := &EncoderTemplateData{
    50  			PackagePath: enc.PackagePath,
    51  			PackageName: pkgName,
    52  			Function:    enc.Function,
    53  			MIMETypes:   enc.MIMETypes,
    54  			Default:     isDefault,
    55  		}
    56  		data[i] = d
    57  	}
    58  	return data, nil
    59  }
    60  
    61  // normalizeEncodingDefinitions figures out the package path and function of all encoding
    62  // definitions and groups them by package and function name.
    63  // We're going for simple rather than efficient (this is codegen after all)
    64  // Also we assume that the encoding definitions have been validated: they have at least
    65  // one mime type and definitions with no package path use known encoders.
    66  func normalizeEncodingDefinitions(defs []*design.EncodingDefinition) []*design.EncodingDefinition {
    67  	// First splat all definitions so each only have one mime type
    68  	var encs []*design.EncodingDefinition
    69  	for _, enc := range defs {
    70  		if len(enc.MIMETypes) == 1 {
    71  			encs = append(encs, enc)
    72  			continue
    73  		}
    74  		for _, m := range enc.MIMETypes {
    75  			encs = append(encs, &design.EncodingDefinition{
    76  				MIMETypes:   []string{m},
    77  				PackagePath: enc.PackagePath,
    78  				Function:    enc.Function,
    79  				Encoder:     enc.Encoder,
    80  			})
    81  		}
    82  	}
    83  
    84  	// Next make sure all definitions have a package path
    85  	for _, enc := range encs {
    86  		if enc.PackagePath == "" {
    87  			mt := enc.MIMETypes[0]
    88  			enc.PackagePath = design.KnownEncoders[mt]
    89  			idx := 0
    90  			if !enc.Encoder {
    91  				idx = 1
    92  			}
    93  			enc.Function = design.KnownEncoderFunctions[mt][idx]
    94  		} else if enc.Function == "" {
    95  			if enc.Encoder {
    96  				enc.Function = "NewEncoder"
    97  			} else {
    98  				enc.Function = "NewDecoder"
    99  			}
   100  		}
   101  	}
   102  
   103  	// Regroup by package and function name
   104  	byfn := make(map[string][]*design.EncodingDefinition)
   105  	var first string
   106  	for _, enc := range encs {
   107  		key := enc.PackagePath + "#" + enc.Function
   108  		if first == "" {
   109  			first = key
   110  		}
   111  		if _, ok := byfn[key]; ok {
   112  			byfn[key] = append(byfn[key], enc)
   113  		} else {
   114  			byfn[key] = []*design.EncodingDefinition{enc}
   115  		}
   116  	}
   117  
   118  	// Reserialize into array keeping the first element identical since it's the default
   119  	// encoder.
   120  	return serialize(byfn, first)
   121  }
   122  
   123  func serialize(byfn map[string][]*design.EncodingDefinition, first string) []*design.EncodingDefinition {
   124  	res := make([]*design.EncodingDefinition, len(byfn))
   125  	i := 0
   126  	keys := make([]string, len(byfn))
   127  	for k := range byfn {
   128  		keys[i] = k
   129  		i++
   130  	}
   131  	sort.Strings(keys)
   132  	var idx int
   133  	for j, k := range keys {
   134  		if k == first {
   135  			idx = j
   136  			break
   137  		}
   138  	}
   139  	keys[0], keys[idx] = keys[idx], keys[0]
   140  	i = 0
   141  	for _, key := range keys {
   142  		encs := byfn[key]
   143  		res[i] = &design.EncodingDefinition{
   144  			MIMETypes:   encs[0].MIMETypes,
   145  			PackagePath: encs[0].PackagePath,
   146  			Function:    encs[0].Function,
   147  		}
   148  		if len(encs) > 0 {
   149  			encs = encs[1:]
   150  			for _, enc := range encs {
   151  				for _, m := range enc.MIMETypes {
   152  					found := false
   153  					for _, rm := range res[i].MIMETypes {
   154  						if m == rm {
   155  							found = true
   156  							break
   157  						}
   158  					}
   159  					if !found {
   160  						res[i].MIMETypes = append(res[i].MIMETypes, m)
   161  					}
   162  				}
   163  			}
   164  		}
   165  		i++
   166  	}
   167  	return res
   168  }