Skip to content

Instantly share code, notes, and snippets.

@fcmendoza
Created May 9, 2019 19:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fcmendoza/835ff97e682a69503757f11191c68e4b to your computer and use it in GitHub Desktop.
Save fcmendoza/835ff97e682a69503757f11191c68e4b to your computer and use it in GitHub Desktop.
Go Go Power Rangers
// See comments for examples.
@fcmendoza
Copy link
Author

fcmendoza commented May 9, 2019

Functions in structs

package main

import (
	"fmt"
	"time"
)

type Employee struct {
	FirstName, LastName string
}

func fullName(firtname string, lastName string) (fullname string) {
	fullname = firtname + " " + lastName
	return
}

func (e Employee) FullName() string { // attaching a method to the Employee struct
	return e.FirstName + " " + e.LastName
}

// DasError is an error implementation that includes a time and message.
type DasError struct {
	When time.Time
	What string
}

func (de DasError) Error() string {
	return fmt.Sprintf("%v: %v", de.When, de.What)
}

func (de DasError) Errore() string {
	return fmt.Sprintf("Errore: %v: %v", de.When, de.What)
}

/*
Because we're returning the bult-in 'error' type the retuning type must implement the Error() method. 
In other words the returning type must implment 'error' for this to work. That is DasError type must have an Error method.
Otherwise the program will fail with the following error:
cannot use DasError literal (type DasError) as type error in return argument:
	DasError does not implement error (missing Error method)
*/
func oops() error { // we're retuning `error` here, therefore DasError must implement `error`, therefore DasError must have an Error method
	return DasError {
		time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
		"the file system has gone away",
	}
}

func ups() DasError {
	de := DasError { time.Date(2019, 3, 15, 22, 30, 0, 0, time.UTC), "The file failed.", }
	return de
}

func main() {
	e := Employee {
		FirstName: "Jon",
		LastName: "Connor",
	}
	fmt.Println(fullName(e.FirstName, e.LastName))
	fmt.Println(e.FullName())

	err := oops();
	if err != nil {
		fmt.Println(err)
	}

	dae := ups()
	msg := dae.Errore()
	fmt.Println(msg)
	fmt.Println(dae)
}

Output:

Jon Connor                                                                                                                                                            
Jon Connor                                                                                                                                                            
1989-03-15 22:30:00 +0000 UTC: the file system has gone away                                                                                                          
Errore: 2019-03-15 22:30:00 +0000 UTC: The file failed.                                                                                                               
2019-03-15 22:30:00 +0000 UTC: The file failed.  

Implicit implementation

I think DasError implements error implicitly by virtue of having an Error method as part of it:

type DasError struct {
	When time.Time
	What string
}

func (de DasError) Error() string {
	return fmt.Sprintf("%v: %v", de.When, de.What)
}

and because we're defining the oops function to return error we can safely return a DasError type here:

func oops() error { 
	return DasError {
		time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
		"the file system has gone away",
	}
}

References

@fcmendoza
Copy link
Author

fcmendoza commented Jun 13, 2019

Push to Kinesis

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kinesis"
)

type testItem struct {
	StatusTime     string
	Groups         []string
	DonationAmount float64
}

func main() {
	donationPtr := flag.Float64("donation-amount", 0, "a float64")
	groupPtr := flag.String("groups", "", "a string")

	flag.Parse()

	groupsEmpty := make([]string, 0)
	groups := strings.Split(*groupPtr, ",")

	fmt.Println("groups:", groups)
	fmt.Println("donation amount:", *donationPtr)

	if *groupPtr != "" {
		start(donationPtr, groups)
	} else {
		start(donationPtr, groupsEmpty)
	}
}

func start(donationAmount *float64, groups []string) {
	name := flag.String("name", "Shadowfax", "Unicorn Name")
	stream := flag.String("stream", "hackathon-donations-datastream", "Stream Name")

	flag.Parse()

	sess := session.Must(
		session.NewSessionWithOptions(
			session.Options{
				SharedConfigState: session.SharedConfigEnable,
			},
		),
	)

	submitData(kinesis.New(sess), name, stream, donationAmount, groups)
}

func submitData(client *kinesis.Kinesis, name, stream *string, donationAmount *float64, groups []string) {
	rand.Seed(time.Now().UnixNano())

	status, _ := json.Marshal(
		&testItem{
			StatusTime:     time.Now().Format("2006-01-02 15:04:05.000"),
			Groups:         groups,
			DonationAmount: *donationAmount,
		},
	)
	putRecordInput := &kinesis.PutRecordInput{
		Data:         append([]byte(status), "\n"...),
		PartitionKey: name,
		StreamName:   stream,
	}

	if _, err := client.PutRecord(putRecordInput); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	fmt.Print(".")
}

You need to have your AWS keys in ~/.aws or set environment keys; for example:

# These keys are invalid in case you're wondering if they work
export AWS_REGION=us-east-2
export AWS_ACCESS_KEY_ID=ASIAZ2AROKC7R6FMSNN5
export AWS_SECRET_ACCESS_KEY=B952zZmzi0/aNmXiKPQ8vaMQ9iAkReZMmZtLlWqd
export AWS_SESSION_TOKEN=FQoGZXIvYXdzEMH//////////wEaDIaAtn7RO5T7O9klzSKyAjOsHvMKi8ex0R5azJ+E+3r11DT/ReK1mPVR4SbBXt4CZSpzs7+/ajzD84MidDyYOaF+GuvTDNQnX89DhCTHqpl10VFlrVqYgeplozJbwC2AR5hy/njZa2637PQb4DmKm02eDx5nC/tJ0Fi8FBPFaD78tC2X0lJUsCTsck5S0FaDhxRClf/wZ0x2xIBL+nfHqWvd9oZTgnYv2du/3QpWH4V/afrsdBX6QTvuxfr8pcSlRzl2nMn7mEvBu+uoVMbzO3hCaqMiWNl4bM7qmjGKQfS47SguL+eBX7P55G+9XEJFJZdop8V0ISfFeRR/8NJjYoyXnxaGXHvgIVHM2PUqsvRKZjOfuZpVL6L3OnYnrnrs6RsGrTLbZWNAY0ce6rSJIFkChRV5y+kZ6K2nmhuWjNwRWCjO3InoBQ==

Then you can run it passing in arguments:

go build push.go
./push -donation-amount=5 -groups=fsfp,frth

@fcmendoza
Copy link
Author

fcmendoza commented Jun 13, 2019

Lambda in Nodejs

const AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();

require('./patch.js'); // apply the patch

var apigwManagementApi = new AWS.ApiGatewayManagementApi({
    apiVersion: '2018-11-29',
    endpoint: '2dbxbx3s5j.execute-api.us-east-2.amazonaws.com' + '/' + 'test'
});

exports.handler = function(event, context) {
    //console.log(event);
    //console.log(JSON.stringify(event, null, 2));
    
    if (event.Records) { // event is Kinesis
      event.Records.forEach(function(record) {
          // Kinesis data is base64 encoded so decode here
          var payload = new Buffer(record.kinesis.data, 'base64').toString('ascii');
          console.log('Decoded payload:', payload);
          
          var data = JSON.parse(payload);
          var groups = data.Groups || [];
          
          var expressions = [];
    
          for (var g of groups) {
            expressions.push(`votes.${g}.vote_count = votes.${g}.vote_count + :v`);
          }
          
          expressions.push("total_donation_amount = total_donation_amount + :d");
          
          var update_expression = "set " + expressions.join(", "); // e.g.: "set votes.fsfp.vote_count = votes.fsfp.vote_count + :v, votes.pow.vote_count = votes.pow.vote_count + :v"
          
          console.log(update_expression);
          
          var update_params = {
              TableName : 'hackathon-donations-api-totals',
              Key:{
                  "TotalsID": 2
              },
              UpdateExpression: update_expression,
              ReturnValues:"UPDATED_NEW"
          };
          
          update_params.ExpressionAttributeValues = groups.length == 0 // can't update vote counts if no groups were provided
            ? { ":d":data.DonationAmount ? data.DonationAmount : 0 } 
            : {
                  ":v":1,
                  ":d":data.DonationAmount ? data.DonationAmount : 0
              };
          
          console.log("Updating the item...");
          dbClient.update(update_params, function(err, data) {
              if (err) {
                  console.error("Unable to update item. Error JSON:", JSON.stringify(err, null, 2));
              } else {
                  console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
              }
          });
          
          //
          // TODO: Another lambda should take care of processing a dyanmo stream (everytime counts are updated) and push to the API Gateway
          //
          
          var table_params = {
            TableName: 'hackathon-donations-api-totals',
            Key: {
              "TotalsID": 2
              }
          };
      
          dbClient.get(table_params, function(err, data) {
              if (err) {
                  console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
              } else {
                  // GetItem succeeded
                  var resp = data.Item;
                  delete resp.TotalsID;
                  //console.log(JSON.stringify(resp));
                  post_to_clients(resp);
                  
              }
          });
      });
    }
    
    if (event.requestContext) { // event is API Gateway
      console.log("not supported for now");
    }
    
    //var groups = ["fsfp", "pow"];
};

function post_to_clients(jsondata) {
  console.log('Posting data:', JSON.stringify(jsondata));
  
  var table_params = {
    TableName: 'hackathon-donations-api-connections',
    Item: {
      'ConnectionID' : {N: '001'}
    }
  };
  
  dbClient.scan(table_params, function (err, data) {
      if (err)
          console.log('error when scanning db.');
      else {
          //console.log('Connections found.');
          for (var i = 0; i < data.Items.length; i++) {
              var connectionId = data.Items[i].ConnectionID;
              console.log("Connection found:", connectionId);
              
              var params = {
                ConnectionId: connectionId,
                Data: JSON.stringify(jsondata)
              };
              
              apigwManagementApi.postToConnection(params, function (err, data) {
                if (err) console.log(err, err.stack); // an error occurred
                else     console.log("Message sent to", params.ConnectionId); // successful response
              });
              
          }
      }
  });
}

Patch.js

require('aws-sdk/lib/node_loader');
var AWS = require('aws-sdk/lib/core');
var Service = AWS.Service;
var apiLoader = AWS.apiLoader;

apiLoader.services['apigatewaymanagementapi'] = {};
AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018-11-29']);
Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018-11-29', {
  get: function get() {
    var model = {
      "metadata": {
        "apiVersion": "2018-11-29",
        "endpointPrefix": "execute-api",
        "signingName": "execute-api",
        "serviceFullName": "AmazonApiGatewayManagementApi",
        "serviceId": "ApiGatewayManagementApi",
        "protocol": "rest-json",
        "jsonVersion": "1.1",
        "uid": "apigatewaymanagementapi-2018-11-29",
        "signatureVersion": "v4"
      },
      "operations": {
        "PostToConnection": {
          "http": {
            "requestUri": "/@connections/{connectionId}",
            "responseCode": 200
          },
          "input": {
            "type": "structure",
            "members": {
              "Data": {
                "type": "blob"
              },
              "ConnectionId": {
                "location": "uri",
                "locationName": "connectionId"
              }
            },
            "required": [
              "ConnectionId",
              "Data"
            ],
            "payload": "Data"
          }
        }
      },
      "shapes": {}
    }
    model.paginators = {
      "pagination": {}
    }
    return model;
  },
  enumerable: true,
  configurable: true
});

module.exports = AWS.ApiGatewayManagementApi;

@fcmendoza
Copy link
Author

fcmendoza commented Aug 2, 2019

Understanding functions

// C# version
string doSomething(string ctx, int timeout) {
	return ctx + timeout.ToString();
}
// Go version
func doSomething(ctx string, timeout int) string {
	return ctx + strconv.Itoa(timeout)
}

@fcmendoza
Copy link
Author

fcmendoza commented Aug 6, 2019

Pointers

Super basic

name := "Jon"
fmt.Println("\nValue of *name* is", name)

fmt.Println("Memory value of *name* is", &name)

ptr := &name
fmt.Println("Pointer to *name* address is", ptr)
Value of *name* is Jon
Memory value of *name* is 0xc0000381d0
Pointer to *name* address is 0xc0000381d0

Basic

func main() {
	name := "Jon"
	fmt.Println("\nValue of *name* is", name)

	fmt.Println("Memory value of *name* is", &name)

	ptr := &name
	fmt.Println("Pointer (ptr) to *name* address is", ptr)

	var pun = &name
	fmt.Println("Pointer (pun) to *name* address is", pun)

	var poi *string = &name // the type *string is optional here
	fmt.Println("Pointer (poi) to *name* address is", poi)

	someByRef(&name)
}

func someByRef(some *string) {
	fmt.Println("Value of *some* is", some, "and its value is", *some)
}
Value of *name* is Jon 
Memory value of *name* is 0xc0000381d0
Pointer (ptr) to *name* address is 0xc0000381d0
Pointer (pun) to *name* address is 0xc0000381d0
Pointer (poi) to *name* address is 0xc0000381d0
Value of *some* is 0xc0000381d0 and its value is Jon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment