github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/api/reqsimport/reqsimport.go (about) 1 package reqsimport 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "path" 9 "time" 10 11 "github.com/ActiveState/cli/internal/language" 12 "github.com/ActiveState/cli/internal/locale" 13 "github.com/ActiveState/cli/internal/logging" 14 "github.com/ActiveState/cli/pkg/platform/api" 15 "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" 16 ) 17 18 const ( 19 jsonContentType = "application/json" 20 ) 21 22 // Opts contains the options available for the primary package type ReqsImport. 23 type Opts struct { 24 ReqsvcURL string 25 } 26 27 // ReqsImport represents a reusable http.Client and related options. 28 type ReqsImport struct { 29 opts Opts 30 client *http.Client 31 } 32 33 // New forms a pointer to a default ReqsImport instance. 34 func New(opts Opts) (*ReqsImport, error) { 35 c := &http.Client{ 36 Timeout: 60 * time.Second, 37 Transport: api.NewRoundTripper(http.DefaultTransport), 38 } 39 40 ri := ReqsImport{ 41 opts: opts, 42 client: c, 43 } 44 45 return &ri, nil 46 } 47 48 // Init is a convenience wrapper for New. 49 func Init() *ReqsImport { 50 svcURL := api.GetServiceURL(api.ServiceRequirementsImport) 51 url := svcURL.Scheme + "://" + path.Join(svcURL.Host, svcURL.Path) 52 53 opts := Opts{ 54 ReqsvcURL: url, 55 } 56 57 ri, err := New(opts) 58 if err != nil { 59 panic(err) 60 } 61 62 return ri 63 } 64 65 // Changeset posts requirements data to a backend service and returns a 66 // Changeset that can be committed to a project. 67 func (ri *ReqsImport) Changeset(data []byte, lang string) ([]*mono_models.CommitChangeEditable, error) { 68 reqPayload := &TranslationReqMsg{ 69 Data: string(data), 70 Language: lang, 71 } 72 if lang == "" { 73 // The endpoint requires a valid language name. It is not present in the requirements read and 74 // returned. When coupled with "unformatted=true", the language has no bearing on the 75 // translation, so just pick one. 76 reqPayload.Language = language.Python3.Requirement() 77 reqPayload.Unformatted = true 78 } 79 respPayload := &TranslationRespMsg{} 80 81 err := postJSON(ri.client, ri.opts.ReqsvcURL, reqPayload, respPayload) 82 if err != nil { 83 return nil, err 84 } 85 86 if len(respPayload.LineErrs) > 0 { 87 return nil, &TranslationResponseError{respPayload.LineErrs} 88 } 89 90 return respPayload.Changeset, nil 91 } 92 93 // TranslationReqMsg represents the message sent to the requirements 94 // translation service. 95 type TranslationReqMsg struct { 96 Data string `json:"requirements"` 97 Language string `json:"language"` 98 Unformatted bool `json:"unformatted"` 99 } 100 101 // TranslationRespMsg represents the message returned by the requirements 102 // translation service. 103 type TranslationRespMsg struct { 104 Changeset []*mono_models.CommitChangeEditable `json:"changeset,omitempty"` 105 LineErrs []TranslationLineError `json:"errors,omitempty"` 106 } 107 108 // TranslationLineError represents an error reported by the requirements 109 // translation service regarding a single line processed from the request. 110 type TranslationLineError struct { 111 ErrMsg string `json:"errorText,omitempty"` 112 PkgTxt string `json:"packageText,omitempty"` 113 } 114 115 // Error implements the error interface. 116 func (e *TranslationLineError) Error() string { 117 return fmt.Sprintf("line %q: %s", e.PkgTxt, e.ErrMsg) 118 } 119 120 // TranslationResponseError contains multiple error messages and allows them to 121 // be handled as a common error. 122 type TranslationResponseError struct { 123 LineErrs []TranslationLineError 124 } 125 126 // Error implements the error interface. 127 func (e *TranslationResponseError) Error() string { 128 var msgs, sep string 129 for _, lineErr := range e.LineErrs { 130 msgs += sep + lineErr.Error() 131 sep = "; " 132 } 133 if msgs == "" { 134 msgs = "unknown error" 135 } 136 137 return locale.Tr("reqsvc_err_line_errors", msgs) 138 } 139 140 func postJSON(client *http.Client, url string, reqPayload, respPayload interface{}) error { 141 var buf bytes.Buffer 142 if err := json.NewEncoder(&buf).Encode(reqPayload); err != nil { 143 return err 144 } 145 146 logging.Debug("POSTing JSON") 147 resp, err := client.Post(url, jsonContentType, &buf) 148 if err != nil { 149 return err 150 } 151 defer resp.Body.Close() //nolint 152 153 return json.NewDecoder(resp.Body).Decode(&respPayload) 154 }