Here is how my basic structure of Todo app looks like called 'main.go';
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
// Todo Struct (Model)
type Todo struct {
Id string `json:"id"`
Task string `json:"task"`
Completed bool `json:"completed"`
}
var todos []Todo
// Get All Todos
func GetTodos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(todos)
}
func main() {
// Init Router
r := mux.NewRouter()
// Mock Data
todos = append(todos, Todo{Id: "1", Task: "FirstTask", Completed: false})
todos = append(todos, Todo{Id: "2", Task: "SecondTask", Completed: false})
fmt.Println("Go dude dude go ")
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello dudes")
})
r.HandleFunc("/api/todos", GetTodos).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", r))
}
And here is my 'main_test.go'
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestGetTodos(t *testing.T) {
req, err := http.NewRequest("GET", "/api/todos", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetTodos)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
expected := `[{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
Problem is in here that I keep getting the following error;
--- FAIL: TestGetTodos (0.00s)
main_test.go:25: handler returned unexpected body: got null
want [{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]
FAIL
exit status 1
FAIL ..filesPath/backend 0.213s
I am definitely missing so simple but couldn't figure out.
When you run your test runs, main is never called, so todos is empty.
var todos []Todo
which is why rr.Body.String() is returning null
If you move your mock data generation code into the GetTodos function you are testing
var todos []Todo
// Get All Todos
func GetTodos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var mockTodos []Todo
mockTodos = append(mockTodos, Todo{Id: "1", Task: "FirstTask", Completed: false})
mockTodos = append(mockTodos, Todo{Id: "2", Task: "SecondTask", Completed: false})
json.NewEncoder(w).Encode(mockTodos)
}
The test will still be failing, but the body will not be null anymore but instead show
=== RUN TestGetTodos
TestGetTodos: main_test.go:25: handler returned unexpected body: got [{"id":"1","task":"FirstTask","completed":false},{"id":"2","task":"SecondTask","completed":false}]
want [{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]
--- FAIL: TestGetTodos (0.00s)
FAIL
And that is because your expectation is not the correct json format. If you replace
expected := `[{Id: "1", Task: "FirstTask", Completed: false},{Id: "2", Task: "SecondTask", Completed: false}]`
with
expected := "[{\"id\":\"1\",\"task\":\"FirstTask\",\"completed\":false},{\"id\":\"2\",\"task\":\"SecondTask\",\"completed\":false}]\n"
That would help you pass the test.
I've added a newline \n at the end of the expected string because json.NewEncoder(w).Encode(todos) adds a newline to the output
Ideally, you do not want to use a global variable to store your state. You would have another object that would store the state or retrieve the state for you(possibly from a DB). In your test, you would initialize this object with your state and then check that your function performs correctly with this state as input.
Below is a simple example of what you could do as a next step.
main.go
// Todo Struct (Model)
type Todo struct {
Id string `json:"id"`
Task string `json:"task"`
Completed bool `json:"completed"`
}
type App struct {
todos []Todo
}
// Get All Todos
func (a *App) GetTodos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(a.todos)
}
func main() {
// Init Router
r := mux.NewRouter()
// Mock Data
app := App{todos: []Todo{
{Id: "1", Task: "FirstTask", Completed: false},
{Id: "2", Task: "SecondTask", Completed: false},
}}
fmt.Println("Go dude dude go ")
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello dudes")
})
r.HandleFunc("/api/todos", app.GetTodos).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", r))
}
main_test.go
func TestGetTodos(t *testing.T) {
req, err := http.NewRequest("GET", "/api/todos", nil)
if err != nil {
t.Fatal(err)
}
// Mock Data
app := App{todos: []Todo{
{Id: "1", Task: "FirstTask", Completed: false},
{Id: "2", Task: "SecondTask", Completed: false},
}}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(app.GetTodos)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
expected := "[{\"id\":\"1\",\"task\":\"FirstTask\",\"completed\":false},{\"id\":\"2\",\"task\":\"SecondTask\",\"completed\":false}]\n"
str := rr.Body.String()
if diff := cmp.Diff(expected , str); diff != "" {
t.Errorf("%s: mismatch (-want +got):\n%s", "", diff)
}
if str != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
Related
I'm trying to import "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" for my main.go file for my lambda function but every time I save the import disappears.
I have some simple Golang code trying to update a visitor count by updating a dynamodb table.
The build keeps failing saying that attributevalue are undefined but I can't save the import for attribute value.
package main
import (
"context"
"log"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
)
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("unable to load SDK config, %v", err)
}
svc := dynamodb.NewFromConfig(cfg)
// Build the request with its input parameters
resp, err := svc.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("table-name"),
Key: map[string]*dynamodb.attributevalue{
"ID": {
S: aws.String("visitors"),
},
},
UpdateExpression: aws.String("ADD visitors :inc"),
ExpressionAttributeValues: map[string]*dynamodb.attributevalue{
":inc": {
N: aws.String("1"),
},
},
})
if err != nil {
log.Fatalf("Got error callingUpdateItem: %s", err)
}
return events.APIGatewayProxyResponse{
Headers: map[string]string{
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*",
},
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(handler)
}
When I do go mod vendor I get
github.com/aws/aws-sdk-go-v2/featrues/dynamodb: no required module provides package github.com/aws/aws-sdk-go-v2/featrues/dynamodb; to add it:
Doing go get github.com/aws/aws-sdk-go-v2/featrues/dynamodb I get
go: module github.com/aws/aws-sdk-go-v2#upgrade found (v1.16.11), but does not contain package github.com/aws/aws-sdk-go-v2/featrues/dynamodb
I am very new to Go and have no idea what to do to resolve this. Any help would be greatly appreciated.
if you check github.com/aws/aws-sdk-go-v2/feature. available packages are dynamodb/attributevalue or dynamodb/expression. Looks like you are using attribute value so the import should have "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
Using the latest localstack-full Docker image, I have stored an encrypted SSM parameter like this:
aws --region us-east-1 --endpoint-url=http://localhost:4566 ssm put-parameter --name "dbpassword" --value "secret2" --type "SecureString"
Then I have implemented a lambda in Go that I can access via API gateway. The implementation looks like this:
package main
import (
"context"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ssm"
"log"
"os"
)
type Event struct {
Name string `json:"name"`
}
func HandleRequest(ctx context.Context, event Event) (events.APIGatewayProxyResponse, error) {
theAwsRegion := os.Getenv("AWS_REGION")
//customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
// return aws.Endpoint{
// PartitionID: "aws",
// URL: "localhost:4566",
// SigningRegion: "us-east-1",
// }, nil
//})
theConfig, err := config.LoadDefaultConfig(
ctx,
config.WithRegion(theAwsRegion),
//config.WithEndpointResolver(customResolver),
)
if err != nil {
log.Fatalf("Unable to load SDK config, %v", err)
}
ssmClient := ssm.NewFromConfig(theConfig)
if ssmClient != nil {
ssmOutput, err := ssmClient.GetParameter(ctx, &ssm.GetParameterInput{Name: aws.String("dbpassword"), WithDecryption: true})
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{"Context-Type": "text/plain"},
Body: "Error occurred getting password",
IsBase64Encoded: false,
}, nil
}
thePassword := ssmOutput.Parameter.Value
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{"Context-Type": "text/plain"},
Body: "Got a SSM client " + *thePassword,
IsBase64Encoded: false,
}, nil
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{"Context-Type": "text/plain"},
Body: "Failed to obtain SSM client",
IsBase64Encoded: false,
}, nil
}
func main() {
lambda.Start(HandleRequest)
}
However, the lambda only responds with "{}" as soon as the call to GetParameter is introduced.
An SSM client is successfully retrieved.
There is also code commented-out attempt to use a custom endpoint resolver, which produces the same result.
Is there a problem with Localstack or what am I doing wrong?
Retrieving the parameter using the AWS CLI works without problems.
Update: I have tried to access the Localstack container from the lambda container over port 4566 and were able to obtain a response from the Localstack container.
I am trying to create an API Gateway connected to a lambda which parses an HTML template with handlebars and then returns it but I am getting this error when I run it locally and even on a test url using AWS.
{
"errorMessage": "invalid character 'e' looking for beginning of value",
"errorType": "SyntaxError"
}
This is my SAM Template
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: data-template-renderer
Parameters:
Stage:
Type: String
AllowedValues:
- dev
- staging
- production
Description: environment values
Resources:
# Defining the API Resource here means we can define the stage name rather than
# always being forced to have staging/prod. Also means we can add a custom domain with
# to the API gateway within this template if needed. Unfortunately there is a side effect
# where it creates a stage named "Stage". This is a known bug and the issue can be
# found at https://github.com/awslabs/serverless-application-model/issues/191
DataTemplateRendererApi:
Type: AWS::Serverless::Api
Properties:
Name: !Sub "${Stage}-data-template-renderer"
StageName: !Ref Stage
DefinitionBody:
swagger: "2.0"
basePath: !Sub "/${Stage}"
info:
title: !Sub "${Stage}-data-template-renderer"
version: "1.0"
consumes:
- application/json
produces:
- application/json
- text/plain
- application/pdf
paths:
/render:
post:
x-amazon-apigateway-integration:
uri:
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RenderTemplate.Arn}/invocations"
httpMethod: POST
type: aws_proxy
x-amazon-apigateway-binary-media-types:
- "*/*"
RenderTemplate:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
ENV: !Ref Stage
Runtime: go1.x
CodeUri: build
Handler: render_template
FunctionName: !Sub 'render_template-${Stage}'
Timeout: 30
Tracing: Active
Events:
RenderTemplateEndpoint:
Type: Api
Properties:
RestApiId: !Ref DataTemplateRendererApi
Path: /render
Method: POST
Policies:
- !Ref S3AccessPolicy
- CloudWatchPutMetricPolicy: {}
S3AccessPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: data-template-renderer-s3-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: s3:GetObject
Resource: !Sub "arn:aws:s3:::*"
- Effect: Allow
Action: s3:PutObject
Resource: !Sub "arn:aws:s3:::*"
And below is the code that I am using for the Lambda. Please forgive the fact that it may not be the best Golang Code you have seen but I am a beginner when it comes to Golang, as I am primarily a PHP dev but the company I work for is creating a lot of Golang lambdas so I started learning it.
package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/aymerick/raymond"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
var sess *session.Session
func init() {
// Setup AWS S3 Session (build once use every function)
sess = session.Must(session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
}))
}
func main() {
lambda.Start(handleRequest)
}
type TemplateRendererRequest struct {
Template string `json:"template"`
Bucket string `json:"bucket"`
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
}
type EmailResponse struct {
Email string `json:"email"`
}
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// Unmarshal json request body into a TemplateRendererRequest struct that mimics the json payload
requestData := TemplateRendererRequest{}
err := json.Unmarshal([]byte(request.Body), &requestData)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
// Get the template object from S3
result, err := s3.New(sess).GetObject(&s3.GetObjectInput{
Bucket: aws.String(requestData.Bucket),
Key: aws.String(requestData.Template),
})
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
defer result.Body.Close()
// The S3 Object Body is of type io.ReadCloser
// below we create a bytes buffer and then convert it to a string so we can use it later
buf := new(bytes.Buffer)
buf.ReadFrom(result.Body)
templateString := buf.String()
// Parse template through Handlebars library
parsedTemplate, err := parseTemplate(templateString, requestData)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
switch requestData.Type {
case "pdf":
return handlePDFRequest(parsedTemplate, requestData)
default:
return handleEmailRequest(parsedTemplate)
}
}
func handleEmailRequest(parsedTemplate string) (events.APIGatewayProxyResponse, error) {
// Put email parsed template in a struct to return in the form of JSON
emailResponse := EmailResponse{
Email: parsedTemplate,
}
// JSON encode the emailResponse
response, err := JSONMarshal(emailResponse)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
return events.APIGatewayProxyResponse{
Body: string(response),
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
}, nil
}
func parseTemplate(templateString string, request TemplateRendererRequest) (string, error) {
result, err := raymond.Render(templateString, request.Data)
return result, err
}
func handlePDFRequest(parsedTemplate string, requestData TemplateRendererRequest) (events.APIGatewayProxyResponse, error) {
pdfBytes, err := GeneratePDF(parsedTemplate)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
keyNameParts := strings.Split(requestData.Template, "/")
keyName := keyNameParts[len(keyNameParts)-1]
pdfName := fmt.Sprintf("%s_%s.pdf", keyName, time.Now().UTC().Format("20060102150405"))
_, err = s3.New(sess).PutObject(&s3.PutObjectInput{
Bucket: aws.String(requestData.Bucket),
Key: aws.String(pdfName),
Body: bytes.NewReader(pdfBytes),
})
b64Pdf := base64.StdEncoding.EncodeToString(pdfBytes)
return events.APIGatewayProxyResponse{
Body: b64Pdf,
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/pdf",
},
IsBase64Encoded: true,
}, nil
}
func GeneratePDF(templateString string) ([]byte, error) {
pdfg, err := wkhtmltopdf.NewPDFGenerator()
if err != nil {
return nil, err
}
// Pass S3 Object body (as reader io.Reader) directly into wkhtmltopdf
pdfg.AddPage(wkhtmltopdf.NewPageReader(strings.NewReader(templateString)))
// Create PDF document in internal buffer
if err := pdfg.Create(); err != nil {
return nil, err
}
// Return PDF as bytes array
return pdfg.Bytes(), nil
}
// https://stackoverflow.com/questions/28595664/how-to-stop-json-marshal-from-escaping-and
func JSONMarshal(t interface{}) ([]byte, error) {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
err := encoder.Encode(t)
return buffer.Bytes(), err
}
I have tried to search high and low for a solution but couldn't find why am I getting this really weird error which is not very helpful.
Thanks for taking the time and helping
Thanks to #Anzel who pointed me in the right direction I decided to look at the request.Body and it seems that by adding the */* to the API Gateway Binary media types, now even the request comes in encoded and lo and behold the first letter was indeed an e as the message was saying.
So I changed this:
// Unmarshal json request body into a TemplateRendererRequest struct that mimics the json payload
requestData := TemplateRendererRequest{}
err := json.Unmarshal([]byte(request.Body), &requestData)
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
To this:
// Unmarshal json request body into a TemplateRendererRequest struct that mimics the json payload
requestData := TemplateRendererRequest{}
b64String, _ := base64.StdEncoding.DecodeString(request.Body)
rawIn := json.RawMessage(b64String)
bodyBytes, err := rawIn.MarshalJSON()
if err != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", err.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, err
}
jsonMarshalErr := json.Unmarshal(bodyBytes, &requestData)
if jsonMarshalErr != nil {
return events.APIGatewayProxyResponse{
Body: fmt.Errorf("Error: %s ", jsonMarshalErr.Error()).Error(),
StatusCode: 400,
Headers: map[string]string{
"Content-Type": "text/plain",
},
}, jsonMarshalErr
}
From what I saw online you could also change this:
rawIn := json.RawMessage(b64String)
bodyBytes, err := rawIn.MarshalJSON()
To this:
[]byte(b64String)
When I now do a CURL request and output it to a file I get the PDF correctly.
I'm writing the test cases of the controllers for my project in golang. In the controllers there is the function name SaveProvider()having parameter c *gin.Context and I don't know how to pass the JSON to the c *gin.Context this parameter and How I test my functions used in the controllers Can anyone tell me that How What is the problem in this code.It is also called table driven test.
package controllers
import (
"bkapiv1/models"
"fmt"
"testing"
"github.com/gin-gonic/gin"
)
func TestSaveProvider(t *testing.T) {
type args struct {
c *gin.Context
json ProviderProfile
}
tests := []struct {
name string
args args
want bool
}{
{
"SaveProvider",
args{
&gin.Context{
//How to pass here a JSON means the below JSON ProviderProfile.
},
ProviderProfile{
models.User{
FirstName: "Harry",
LastName: "Potter",
FullName: "Harry Potter",
CompanyName: "TheIronNetwork",
EmailId: "harry#gmail.com",
Password: "vadhera123",
},
models.AddressStruct{},
models.Provider{
ProviderCategory: "IC",
Priority: 1,
},
},
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
SaveProvider(tt.args.c)
})
}
}
The function in the controller is:-
func SaveProvider(c *gin.Context){
}
there is a library for what you want to do
handler := func(w http.ResponseWriter, r *http.Request) {
c := CreateTestContext(w)
SaveProvider(c)
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(resp.Header.Get("Content-Type"))
fmt.Println(string(body))
I am fetching the facebook profile picture like this
let params = ["height": 300, "width": 300, "redirect": false] as [String : Any]
let graphRequest = FBSDKGraphRequest(graphPath: "me/picture", parameters: params, httpMethod: "GET")
let connection = FBSDKGraphRequestConnection()
connection.add(graphRequest, completionHandler: { (connection, result, error) in
if error == nil {
print("profile pic ************\(result)")
}
})
connection.start()
Now I need to access the profile pictures from the ablum and set those in my collectionView . I tried to set the graphPath: "me/album/profile_picture" but no luck . Could anybody give me some hints please ?
You need to find the id of the "Profile Pictures" album and request the photos for that id.
Facebook.albums(user: "me").request({ albums in
albums.first(where: { ($0["name"] as? String) == "Profile Pictures" }).flatMap({
Facebook.photos(album: $0["id"] as! String).request({ photos in
print(photos)
})
})
})
I have made an enum below to make requesting graph api paths easier.
enum Facebook: Graphable {
case albums(user: String)
case photos(album: String)
var path: String {
switch self {
case let .albums(uid):
return "\(uid)/albums"
case let .photos(aid):
return "\(aid)/photos"
}
}
var method: String {
switch self {
case .albums, .photos:
return "GET"
}
}
var params: [String : Any] {
switch self {
case .albums:
return [:]
case .photos:
return [
"height": 300,
"width": 300,
"redirect": false
]
}
}
}
Here is the protocol and extension the enum must conform to.
protocol Graphable {
var path: String { get }
var method: String { get}
var params: [String : Any] { get }
}
extension Graphable {
typealias JSON = [String:Any]
func request(_ handler: #escaping ([JSON]!) -> (), failure: #escaping (Error) -> () = { print($0) }) {
let connection = FBSDKGraphRequestConnection()
let request = FBSDKGraphRequest(
graphPath: path,
parameters: params,
httpMethod: method
)
connection.add(request) {
_ = $0.1.map ({ handler(($0 as? JSON)?["data"] as? [JSON]) }) ??
$0.2.map ({ failure($0) })
}
connection.start()
}
}
I got my answer like this :
import FBSDKCoreKit
import SwiftyJSON
func getFBAlbumID() {
let graphRequest = FBSDKGraphRequest(graphPath: "me/albums", parameters: nil, httpMethod: "GET")
let connection = FBSDKGraphRequestConnection()
connection.add(graphRequest, completionHandler: { (connection, result, error) in
if error == nil
let dictionary = JSON(result)
// print("albums ID are **************\(dictionary)")
if let data = dictionary["data"].array {
print("data of profilePicture ******* \(data)")
if let dict = data.first(where: { ($0["name"].string ) == "Profile Pictures" }) {
let id = dict["id"].string
print("my desired id : ********* \(id)")
self.getFBAlbumPhoto(albumID: id!)
}
}
}
})
connection.start()
}
func getFBAlbumPhoto(albumID: String) {
let params = [ "height": 300, "width": 300, "redirect": false] as [String : Any]
let graphRequest = FBSDKGraphRequest(graphPath: "\(albumID)/photos?fields=source", parameters: params, httpMethod: "GET")
let connection = FBSDKGraphRequestConnection()
connection.add(graphRequest, completionHandler: { (connection, result, error) in
if error == nil {
//print(result)
let dictionary = JSON(result)
print("result are **************\(dictionary)")
}
})
connection.start()
}
Here I am grabbing the ID of the particular album I desire and then make another call to get the photos of that album. Here I am calling a function from inside another function. If someone can help me to write the code in more Swift way that would be helpful, like using closure. Thanks.