-
Alexey Lunin authoredd4e5e218
main.go 17.25 KiB
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"unicode"
"unicode/utf8"
"code.vereign.com/code/restful-api/server"
)
func main() {
newpath := filepath.Join(".", "javascript/temp")
os.MkdirAll(newpath, os.ModePerm)
viamapi := buildViamAPI()
err := ioutil.WriteFile("./javascript/temp/viamapi.js", []byte(viamapi), 0644)
if err != nil {
fmt.Println(err.Error())
}
penpalMethods := buildPenpalMethods()
err = ioutil.WriteFile("./javascript/temp/penpal-methods.js", []byte(penpalMethods), 0644)
if err != nil {
fmt.Println(err.Error())
}
}
func buildPenpalMethods() string {
prefixes := []string{}
endPoints := server.GetEndPoints(prefixes)
result :=
"import Penpal from 'penpal';\n\n" +
"export default {\n"
methods := generatePenpalRemoteMethods(endPoints)
result += methods
result += "\n}"
return result
}
func buildViamAPI() string {
prefixes := []string{}
endPoints := server.GetEndPoints(prefixes)
result := "const axios = require('axios');\n"
result += "const merge = require('lodash/merge');\n"
var keys []string
for k := range endPoints {
keys = append(keys, k)
}
sort.Strings(keys)
keysLen := len(keys)
result += "function ViamAPI() {\n" +
" this.config = {\n" +
" headers: {\n" +
" 'publicKey': '',\n" +
" 'uuid': '',\n" +
" 'deviceHash': '',\n" +
" 'token': ''\n" +
" }\n" +
" }\n" +
"}\n\n"
result += "ViamAPI.prototype.setSessionData = function(uuid, token) {\n" +
" this.config.headers.uuid = uuid;\n" +
" this.config.headers.token = token;\n" +
"};\n\n"
result += "ViamAPI.prototype.setDeviceHash = function(deviceHash) {\n" +
" this.config.headers.deviceHash = deviceHash;\n" +
"};\n\n"
result += "ViamAPI.prototype.setIdentity = function(authenticationPublicKey) {\n" +
" this.config.headers.publicKey = window.btoa(authenticationPublicKey);\n" +
"};\n\n"
result += "ViamAPI.prototype.getConfig = function() {\n" +
" return this.config;\n" +
"};\n\n"
for i := 0; i < keysLen; i++ {
url := keys[i]
if endPoints[url].Form != nil {
splits := strings.Split(url, "/")
packageStr := splits[len(splits)-2]
methodStr := splits[len(splits)-1]
form := endPoints[url].Form
s := reflect.ValueOf(form)
typeOfT := s.Type()
fields := typeFields(typeOfT)
lenFields := len(fields)
args := ""
for i := 0; i < lenFields; i++ {
if i != lenFields-1 {
args += fmt.Sprintf("%sArg,", fields[i].name)
} else {
args += fmt.Sprintf("%sArg", fields[i].name)
}
}
lastComma := ","
if args == "" {
lastComma = ""
}
result += "ViamAPI.prototype." + packageStr + strings.Title(methodStr) + " = function(config" + lastComma + args + ") {\n" +
" return axios.post(window.API_HOST + '" + packageStr + "/" + methodStr + "', {\n"
for i := 0; i < lenFields; i++ {
result += " " + fields[i].name + ": " + fields[i].name + "Arg"
if i != lenFields-1 {
result += ","
}
result += "\n"
}
result += " }, merge({}, this.config, config));\n" +
"};\n\n"
} else {
splits := strings.Split(url, "/")
packageStr := splits[len(splits)-2]
/*if !packageCreated[packageStr] {
result += "api[\"" + packageStr + "\"] = {}\n"
packageCreated[packageStr] = true
}*/
methodStr := splits[len(splits)-1]
result += "ViamAPI.prototype." + packageStr + strings.Title(methodStr) + " = function(config, data) {\n" +
" return axios.post(window.API_HOST + '" + packageStr + "/" + methodStr + "', data || {}, merge({}, this.config, config));\n" +
"};\n\n"
}
}
result += "module.exports = ViamAPI;\n"
return result
}
const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a'
smallLongEss = '\u017f'
)
// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
for _, sb := range s {
if len(t) == 0 {
return false
}
tb := t[0]
if tb < utf8.RuneSelf {
if sb != tb {
sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask {
return false
}
} else {
return false
}
}
t = t[1:]
continue
}
// sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t)
switch sb {
case 's', 'S':
if tr != smallLongEss {
return false
}
case 'k', 'K':
if tr != kelvin {
return false
}
default:
return false
}
t = t[size:]
}
if len(t) > 0 {
return false
}
return true
}
// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, sb := range s {
tb := t[i]
if sb == tb {
continue
}
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask {
return false
}
} else {
return false
}
}
return true
}
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, b := range s {
if b&caseMask != t[i]&caseMask {
return false
}
}
return true
}
func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false
special := false // special letter
for _, b := range s {
if b >= utf8.RuneSelf {
return bytes.EqualFold
}
upper := b & caseMask
if upper < 'A' || upper > 'Z' {
nonLetter = true
} else if upper == 'K' || upper == 'S' {
// See above for why these letters are special.
special = true
}
}
if special {
return equalFoldRight
}
if nonLetter {
return asciiEqualFold
}
return simpleLetterEqualFold
}
// A field represents a single field found in a struct.
type field struct {
name string
nameBytes []byte // []byte(name)
equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
tag bool
index []int
typ reflect.Type
omitEmpty bool
quoted bool
}
func fillField(f field) field {
f.nameBytes = []byte(f.name)
f.equalFold = foldFunc(f.nameBytes)
return f
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) []field {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
count := map[reflect.Type]int{}
nextCount := map[reflect.Type]int{}
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
isUnexported := sf.PkgPath != ""
if sf.Anonymous {
t := sf.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if isUnexported && t.Kind() != reflect.Struct {
// Ignore embedded fields of unexported non-struct types.
continue
}
// Do not ignore embedded fields of unexported struct types
// since they may have exported fields.
} else if isUnexported {
// Ignore unexported non-embedded fields.
continue
}
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
// Follow pointer.
ft = ft.Elem()
}
// Only strings, floats, integers, and booleans can be quoted.
quoted := false
if opts.Contains("string") {
switch ft.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.String:
quoted = true
}
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = sf.Name
}
fields = append(fields, fillField(field{
name: name,
tag: tagged,
index: index,
typ: ft,
omitEmpty: opts.Contains("omitempty"),
quoted: quoted,
}))
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
}
}
}
}
sort.Slice(fields, func(i, j int) bool {
x := fields
// sort field by name, breaking ties with depth, then
// breaking ties with "name came from json tag", then
// breaking ties with index sequence.
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
})
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
return fields
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order. The winner
// must therefore be one with the shortest index length. Drop all
// longer entries, which is easy: just truncate the slice.
length := len(fields[0].index)
tagged := -1 // Index of first tagged field.
for i, f := range fields {
if len(f.index) > length {
fields = fields[:i]
break
}
if f.tag {
if tagged >= 0 {
// Multiple tagged fields at the same level: conflict.
// Return no field.
return field{}, false
}
tagged = i
}
}
if tagged >= 0 {
return fields[tagged], true
}
// All remaining fields have the same length. If there's more than one,
// we have a conflict (two fields named "X" at the same level) and we
// return no field.
if len(fields) > 1 {
return field{}, false
}
return fields[0], true
}
func generatePenpalRemoteMethods(endPoints map[string]*server.EndPoint) string {
var keys []string
for k := range endPoints {
keys = append(keys, k)
}
sort.Strings(keys)
keysLen := len(keys)
methods := ""
privateCheckSnippet := `
const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
if (authenticationPublicKey === null) {
result({
"data" : "",
"code" : "400",
"status" : "Identity not authenticated"
});
}
if (loadedIdentities[authenticationPublicKey] === null) {
result({
"data" : "",
"code" : "400",
"status" : "Identity not authenticated"
});
}
const success = extendPinCodeTtl(authenticationPublicKey);
if(success === false) {
result({"data" : "",
"code" : "400",
"status" : "Identity not authenticated"
})
}
`
for i := 0; i < keysLen; i++ {
url := keys[i]
if endPoints[url].ManuallyWritten == true {
continue
}
if url == "/identity/getIdentityProfileData" {
privateCheckSnippet = `
const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
if (authenticationPublicKey === null) {
result({
"data" : "",
"code" : "400",
"status" : "Identity not authenticated"
});
}
if (loadedIdentities[authenticationPublicKey] === null) {
result({
"data" : "",
"code" : "400",
"status" : "Identity not authenticated"
});
}
`
}
if endPoints[url].Form != nil {
splits := strings.Split(url, "/")
packageStr := splits[len(splits)-2]
methodStr := splits[len(splits)-1]
form := endPoints[url].Form
s := reflect.ValueOf(form)
typeOfT := s.Type()
fields := typeFields(typeOfT)
lenFields := len(fields)
args := ""
for i := 0; i < lenFields; i++ {
if i != lenFields-1 {
args += fmt.Sprintf("%sArg,", fields[i].name)
} else {
args += fmt.Sprintf("%sArg", fields[i].name)
}
}
snippet := ""
if endPoints[url].HandlerType == "private" {
snippet = privateCheckSnippet
}
lastComma := ","
if args == "" {
lastComma = ""
}
method := packageStr + strings.Title(methodStr) + ": function(" + args + ") {\n" +
" return new Penpal.Promise(function(result) {\n" + snippet +
" executeRestfulFunction(\"" + endPoints[url].HandlerType + "\", viamApi, viamApi." + packageStr + strings.Title(methodStr) + ", null" + lastComma + args + ").then(function(executeResult) {\n" +
" result(executeResult);\n" +
" });\n" +
" });\n" +
"}"
methods += method
if i != keysLen-1 {
methods += ","
}
methods += "\n"
} else {
splits := strings.Split(url, "/")
packageStr := splits[len(splits)-2]
methodStr := splits[len(splits)-1]
snippet := ""
if endPoints[url].HandlerType == "private" {
snippet = privateCheckSnippet
}
method := packageStr + strings.Title(methodStr) + ": function() {\n" +
" return new Penpal.Promise(function(result) {\n" + snippet +
" executeRestfulFunction(\"" + endPoints[url].HandlerType + "\", viamApi, viamApi." + packageStr + strings.Title(methodStr) + ", null).then(function(executeResult) {\n" +
" result(executeResult);\n" +
" });\n" +
" });\n" +
"}"
methods += method
if i != keysLen-1 {
methods += ","
}
methods += "\n"
}
}
return methods
}