DigitalOcean Functions Challenge를 완료하기 위한 Go CLI 작성
29926 단어 godigitaloceanfunctionsapi
도전
DigitalOcean은 최근 serverless functions 및 released a challenge 버전을 출시하여 선보이기도 했습니다. 도전을 거절하는 것을 좋아하지 않는 사람으로서 저는 Go CLI를 사용하여 DigitalOcean의 도전을 받아들였습니다.
시작하기
첫째, challenge website에 대한 챌린지를 읽는 것은 매우 간단했지만 몇 가지 질문에 답해야 했습니다.
첫째, challenge website에 대한 챌린지를 읽는 것은 매우 간단했지만 몇 가지 질문에 답해야 했습니다.
Content-Type
및 Accepted
헤더는 이 응용 프로그램이 JSON 데이터를 수락하고 다시 전송한다고 알려주지만 전체 문서의 지침에는 매개 변수 파일이 JSON 문서라는 것 외에는 실제로 언급되어 있지 않습니다. 계속해서 API가 JSON을 수락하고 다시 보낸다고 생각하여 CLI를 작성했습니다. 나는 옳았지만 여기의 문서는 완전히 명확하지 않았습니다. 코드
이 경우 이것은 그냥 버리는 앱이었기 때문에 모든 것을 main.go
파일에 작성하고 go mod init <github.com/your_handle/project_name>
가 있는 모듈을 켭니다.
가장 먼저 할 일은 지침에 설명된 대로 name
및 type
매개변수를 허용하는 CLI로 시작하는 것이었습니다. 저는 이 플래그를 init
함수에 넣고 몇 가지 모듈 변수를 로드하기로 했습니다. 그래서 실제로 많은 작업을 수행할 필요가 없었고 앱이 실행될 때마다 플래그가 실행될 것으로 예상했습니다.
// main.go
package main
import (
"flag"
)
var (
sammyname string
sammytype string
)
const (
API_URL = "https://functionschallenge.digitalocean.com/api/sammy"
)
func init() {
flag.StringVar(&sammyname, "name", "", "The name to give your new Sammy.")
flag.StringVar(&sammytype, "type", "", "The type to assign to your new Sammy.")
flag.Parse()
}
func main() {}
go run main.go
로 앱을 실행하면 이제 명령줄 플래그가 제공됩니다.
문서를 다시 살펴보면 Sammy Type 매개변수는 실제로 열거형이므로 유형과 몇 가지 도우미 함수를 만들고 변수를 적절하게 설정하는 데 사용했습니다.
// main.go
...
import (
"fmt"
"log"
"flag"
"strings"
)
var (
sammyname string
sammytype sammyType
)
type (
sammyType string
)
const (
API_URL = "https://functionschallenge.digitalocean.com/api/sammy"
sammyType_Sammy sammyType = "sammy"
sammyType_Punk sammyType = "punk"
sammyType_Dinosaur sammyType = "dinosaur"
sammyType_Retro sammyType = "retro"
sammyType_Pizza sammyType = "pizza"
sammyType_Robot sammyType = "robot"
sammyType_Pony sammyType = "pony"
sammyType_Bootcamp sammyType = "bootcamp"
sammyType_XRay sammyType = "xray"
)
func NewSammyType(s string) (sammyType, error) {
var t sammyType
switch strings.ToLower(s) {
case "sammy":
t = sammyType_Sammy
case "punk":
t = sammyType_Punk
case "dinosaur":
t = sammyType_Dinosaur
case "retro":
t = sammyType_Retro
case "pizza":
t = sammyType_Pizza
case "robot":
t = sammyType_Robot
case "pony":
t = sammyType_Pony
case "bootcamp":
t = sammyType_Bootcamp
case "xray":
t = sammyType_XRay
default:
return "", fmt.Errorf("%s is an invalid sammyType", s)
}
return t, nil
}
func (s sammyType) String() string {
return string(s)
}
func init() {
flag.StringVar(&sammyname, "name", "", "The name to give your new Sammy.")
st := flag.String("type", "", "The type to assign to your new Sammy.")
flag.Parse()
sammyType, err := NewSammyType(*st)
if err != nil {
log.Fatal(err)
}
}
...
이제 API에 맞는 유형이 있습니다.
다음으로 요청을 나타내는 구조체와 거기에 몇 가지 도우미 함수를 간단히 작성했습니다.
// main.go
...
import (
"fmt"
"log"
"flag"
"strings"
"bytes"
"encoding/json"
"io"
"io/ioutil"
)
...
type (
sammyType string
sharksRequest struct {
Name string `json:"name"`
Type string `json:"name"`
}
)
// func NewSharksRequest() is something that I normally write, but there was really no point in this case.
func (req *sharksRequest) setName(name string) *sharksRequest {
req.Name = name
}
func (req *sharksRequest) setType(t sammyType) *sharksRequest {
req.Type = t.String()
}
func (req *sharksRequest) marshalJSON() ([]byte, error) {
return json.Marshal(req)
}
...
이제 API로 보낼 것이 생겼습니다. 따라서 요청을 보내고 출력이 전혀 문서화되지 않았기 때문에 출력이 무엇인지 확인하는 문제였습니다(내가 볼 수 있음).
// main.go
...
func main() {
// Set some variables to use:
// Mostly we need the set up the base request struct and
// the http client.
var (
c := http.DefaultClient
r := &sharksRequest{
Name: sammyname,
}
)
// We can use our helper function to make sure we are
// getting correct values here.
r.setType(NewSammyType(sammytype))
// marshal the struct to JSON
rb, err := r.marshalJSON()
if err != nil {
log.Fatal(err)
}
// Build the new http request
req, err := http.NewRequest(http.MethodPost, API_URL, bytes.NewBuffer(rb))
if err != nil {
log.Fatal(err)
}
// Set the required headers (from the docs).
req.Header = map[string][]string{
"Accept": []string{"application/json"},
"Content-Type": []string{"application/json"},
}
// Send the request
resp, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
// Because we don't know the structure of the output
// we can just output the response to the terminal.
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
log.Println(string(out))
}
go rungo run main.go -name <myname> -type <mytype>
을 통해 코드를 실행하면 이 시점에서 실제로 작동하지만 출력하는 원시 바이트 문자열이 마음에 들지 않습니다. 실제 API 처리기에서 우리는 응답을 구조체에 넣기를 원하므로 이제 일부 출력을 볼 수 있으므로 구조체를 빌드할 수 있습니다.
나를 위한 출력은 다음과 같이 성공했습니다.
{"message":"Shark created successfully! Congrats on successfully completing the functions challenge!"}
이것은 실패에 대한 것입니다.
{"message":"The name has already been taken.","errors":{"name":["The name has already been taken."]}}
따라서 이 정보를 염두에 두고 해당 정보를 로드하기 위한 또 다른 구조체와 도우미 메서드를 작성했습니다.
// main.go
...
type (
sammyType string
sharksRequest struct {
Name string `json:"name"`
Type string `json:"name"`
}
sharksResponse struct {
// Message seems pretty strait forward and exists
// in both the responses, so this is a pretty safe
// bet
Message string `json:"message"`
// Errors on the other hand, I don't have enough
// info to strongly type the response, but the
// map type here works for now. I can change it
// later if there is more I want to do with the
// errors.
Errors map[string][]string `json:"errors"`
}
// func NewSharksResponse() is something again that I would normally write, but it isn't necessary in this example
func (resp *sharksResponse) unmarshalJSON(body io.ReadCloser) error {
b, err := ioutil.ReadAll(body)
if err != nil {
return fmt.Errorf("unable to read http body: %w", err)
}
return json.Unmarshal(b, resp)
}
)
...
이제 이것이 있으므로 기본 기능에 추가할 수 있습니다.
// main.go
...
func main() {
// Set some variables to use:
// Mostly we need the set up the base request struct,
// the response struct, and the http client.
var (
c := http.DefaultClient
r := &sharksRequest{
Name: sammyname,
}
R := &sharksResponse{}
)
// We can use our helper function to make sure we are
// getting correct values here.
r.setType(NewSammyType(sammytype))
// marshal the struct to JSON
rb, err := r.marshalJSON()
if err != nil {
log.Fatal(err)
}
// Build the new http request
req, err := http.NewRequest(http.MethodPost, API_URL, bytes.NewBuffer(rb))
if err != nil {
log.Fatal(err)
}
// Set the required headers (from the docs).
req.Header = map[string][]string{
"Accept": []string{"application/json"},
"Content-Type": []string{"application/json"},
}
// Send the request
resp, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
// We have a structure to unmarshal to now, so lets use
// it.
defer resp.Body.Close()
if err := R.unmarshalJSON(resp.Body); err != nil {
log.Fatal(err)
}
// Now we can send some better info to the terminal
if len(R.Errors) > 0 {
log.Fatalf("An error occurred. Message: %s, Errors: %v", R.Message, R.Errors)
}
log.Println(R.Message)
}
그리고 그것으로 우리는 성공적인 응용 프로그램을 가지고 있다고 말하고 싶습니다. 전체 코드를 보려면 여기에서 도전 과제에 대한 내 저장소를 확인하십시오: https://github.com/j4ng5y/digitalocean-functions-challenge
이 리포지토리에도 더 많은 언어를 추가할 예정이므로 README의 지침에 따라 다른 예제를 살펴보세요(제가 만들 때). 다른 언어에 대해서도 비슷한 글을 쓸 것입니다.
Reference
이 문제에 관하여(DigitalOcean Functions Challenge를 완료하기 위한 Go CLI 작성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/j4ng5y/writing-a-go-cli-to-complete-the-digital-ocean-functions-challenge-3372
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
// main.go
package main
import (
"flag"
)
var (
sammyname string
sammytype string
)
const (
API_URL = "https://functionschallenge.digitalocean.com/api/sammy"
)
func init() {
flag.StringVar(&sammyname, "name", "", "The name to give your new Sammy.")
flag.StringVar(&sammytype, "type", "", "The type to assign to your new Sammy.")
flag.Parse()
}
func main() {}
// main.go
...
import (
"fmt"
"log"
"flag"
"strings"
)
var (
sammyname string
sammytype sammyType
)
type (
sammyType string
)
const (
API_URL = "https://functionschallenge.digitalocean.com/api/sammy"
sammyType_Sammy sammyType = "sammy"
sammyType_Punk sammyType = "punk"
sammyType_Dinosaur sammyType = "dinosaur"
sammyType_Retro sammyType = "retro"
sammyType_Pizza sammyType = "pizza"
sammyType_Robot sammyType = "robot"
sammyType_Pony sammyType = "pony"
sammyType_Bootcamp sammyType = "bootcamp"
sammyType_XRay sammyType = "xray"
)
func NewSammyType(s string) (sammyType, error) {
var t sammyType
switch strings.ToLower(s) {
case "sammy":
t = sammyType_Sammy
case "punk":
t = sammyType_Punk
case "dinosaur":
t = sammyType_Dinosaur
case "retro":
t = sammyType_Retro
case "pizza":
t = sammyType_Pizza
case "robot":
t = sammyType_Robot
case "pony":
t = sammyType_Pony
case "bootcamp":
t = sammyType_Bootcamp
case "xray":
t = sammyType_XRay
default:
return "", fmt.Errorf("%s is an invalid sammyType", s)
}
return t, nil
}
func (s sammyType) String() string {
return string(s)
}
func init() {
flag.StringVar(&sammyname, "name", "", "The name to give your new Sammy.")
st := flag.String("type", "", "The type to assign to your new Sammy.")
flag.Parse()
sammyType, err := NewSammyType(*st)
if err != nil {
log.Fatal(err)
}
}
...
// main.go
...
import (
"fmt"
"log"
"flag"
"strings"
"bytes"
"encoding/json"
"io"
"io/ioutil"
)
...
type (
sammyType string
sharksRequest struct {
Name string `json:"name"`
Type string `json:"name"`
}
)
// func NewSharksRequest() is something that I normally write, but there was really no point in this case.
func (req *sharksRequest) setName(name string) *sharksRequest {
req.Name = name
}
func (req *sharksRequest) setType(t sammyType) *sharksRequest {
req.Type = t.String()
}
func (req *sharksRequest) marshalJSON() ([]byte, error) {
return json.Marshal(req)
}
...
// main.go
...
func main() {
// Set some variables to use:
// Mostly we need the set up the base request struct and
// the http client.
var (
c := http.DefaultClient
r := &sharksRequest{
Name: sammyname,
}
)
// We can use our helper function to make sure we are
// getting correct values here.
r.setType(NewSammyType(sammytype))
// marshal the struct to JSON
rb, err := r.marshalJSON()
if err != nil {
log.Fatal(err)
}
// Build the new http request
req, err := http.NewRequest(http.MethodPost, API_URL, bytes.NewBuffer(rb))
if err != nil {
log.Fatal(err)
}
// Set the required headers (from the docs).
req.Header = map[string][]string{
"Accept": []string{"application/json"},
"Content-Type": []string{"application/json"},
}
// Send the request
resp, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
// Because we don't know the structure of the output
// we can just output the response to the terminal.
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
log.Println(string(out))
}
// main.go
...
type (
sammyType string
sharksRequest struct {
Name string `json:"name"`
Type string `json:"name"`
}
sharksResponse struct {
// Message seems pretty strait forward and exists
// in both the responses, so this is a pretty safe
// bet
Message string `json:"message"`
// Errors on the other hand, I don't have enough
// info to strongly type the response, but the
// map type here works for now. I can change it
// later if there is more I want to do with the
// errors.
Errors map[string][]string `json:"errors"`
}
// func NewSharksResponse() is something again that I would normally write, but it isn't necessary in this example
func (resp *sharksResponse) unmarshalJSON(body io.ReadCloser) error {
b, err := ioutil.ReadAll(body)
if err != nil {
return fmt.Errorf("unable to read http body: %w", err)
}
return json.Unmarshal(b, resp)
}
)
...
// main.go
...
func main() {
// Set some variables to use:
// Mostly we need the set up the base request struct,
// the response struct, and the http client.
var (
c := http.DefaultClient
r := &sharksRequest{
Name: sammyname,
}
R := &sharksResponse{}
)
// We can use our helper function to make sure we are
// getting correct values here.
r.setType(NewSammyType(sammytype))
// marshal the struct to JSON
rb, err := r.marshalJSON()
if err != nil {
log.Fatal(err)
}
// Build the new http request
req, err := http.NewRequest(http.MethodPost, API_URL, bytes.NewBuffer(rb))
if err != nil {
log.Fatal(err)
}
// Set the required headers (from the docs).
req.Header = map[string][]string{
"Accept": []string{"application/json"},
"Content-Type": []string{"application/json"},
}
// Send the request
resp, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
// We have a structure to unmarshal to now, so lets use
// it.
defer resp.Body.Close()
if err := R.unmarshalJSON(resp.Body); err != nil {
log.Fatal(err)
}
// Now we can send some better info to the terminal
if len(R.Errors) > 0 {
log.Fatalf("An error occurred. Message: %s, Errors: %v", R.Message, R.Errors)
}
log.Println(R.Message)
}
Reference
이 문제에 관하여(DigitalOcean Functions Challenge를 완료하기 위한 Go CLI 작성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/j4ng5y/writing-a-go-cli-to-complete-the-digital-ocean-functions-challenge-3372텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)