mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			146 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package cjson
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"regexp"
 | 
						|
	"sort"
 | 
						|
)
 | 
						|
 | 
						|
/*
 | 
						|
encodeCanonicalString is a helper function to canonicalize the passed string
 | 
						|
according to the OLPC canonical JSON specification for strings (see
 | 
						|
http://wiki.laptop.org/go/Canonical_JSON).  String canonicalization consists of
 | 
						|
escaping backslashes ("\") and double quotes (") and wrapping the resulting
 | 
						|
string in double quotes (").
 | 
						|
*/
 | 
						|
func encodeCanonicalString(s string) string {
 | 
						|
	re := regexp.MustCompile(`([\"\\])`)
 | 
						|
	return fmt.Sprintf("\"%s\"", re.ReplaceAllString(s, "\\$1"))
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
encodeCanonical is a helper function to recursively canonicalize the passed
 | 
						|
object according to the OLPC canonical JSON specification (see
 | 
						|
http://wiki.laptop.org/go/Canonical_JSON) and write it to the passed
 | 
						|
*bytes.Buffer.  If canonicalization fails it returns an error.
 | 
						|
*/
 | 
						|
func encodeCanonical(obj interface{}, result *bytes.Buffer) (err error) {
 | 
						|
	// Since this function is called recursively, we use panic if an error occurs
 | 
						|
	// and recover in a deferred function, which is always called before
 | 
						|
	// returning. There we set the error that is returned eventually.
 | 
						|
	defer func() {
 | 
						|
		if r := recover(); r != nil {
 | 
						|
			err = errors.New(r.(string))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	switch objAsserted := obj.(type) {
 | 
						|
	case string:
 | 
						|
		result.WriteString(encodeCanonicalString(objAsserted))
 | 
						|
 | 
						|
	case bool:
 | 
						|
		if objAsserted {
 | 
						|
			result.WriteString("true")
 | 
						|
		} else {
 | 
						|
			result.WriteString("false")
 | 
						|
		}
 | 
						|
 | 
						|
	// The wrapping `EncodeCanonical` function decodes the passed json data with
 | 
						|
	// `decoder.UseNumber` so that any numeric value is stored as `json.Number`
 | 
						|
	// (instead of the default `float64`). This allows us to assert that it is a
 | 
						|
	// non-floating point number, which are the only numbers allowed by the used
 | 
						|
	// canonicalization specification.
 | 
						|
	case json.Number:
 | 
						|
		if _, err := objAsserted.Int64(); err != nil {
 | 
						|
			panic(fmt.Sprintf("Can't canonicalize floating point number '%s'",
 | 
						|
				objAsserted))
 | 
						|
		}
 | 
						|
		result.WriteString(objAsserted.String())
 | 
						|
 | 
						|
	case nil:
 | 
						|
		result.WriteString("null")
 | 
						|
 | 
						|
	// Canonicalize slice
 | 
						|
	case []interface{}:
 | 
						|
		result.WriteString("[")
 | 
						|
		for i, val := range objAsserted {
 | 
						|
			if err := encodeCanonical(val, result); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if i < (len(objAsserted) - 1) {
 | 
						|
				result.WriteString(",")
 | 
						|
			}
 | 
						|
		}
 | 
						|
		result.WriteString("]")
 | 
						|
 | 
						|
	case map[string]interface{}:
 | 
						|
		result.WriteString("{")
 | 
						|
 | 
						|
		// Make a list of keys
 | 
						|
		var mapKeys []string
 | 
						|
		for key := range objAsserted {
 | 
						|
			mapKeys = append(mapKeys, key)
 | 
						|
		}
 | 
						|
		// Sort keys
 | 
						|
		sort.Strings(mapKeys)
 | 
						|
 | 
						|
		// Canonicalize map
 | 
						|
		for i, key := range mapKeys {
 | 
						|
			// Note: `key` must be a `string` (see `case map[string]interface{}`) and
 | 
						|
			// canonicalization of strings cannot err out (see `case string`), thus
 | 
						|
			// no error handling is needed here.
 | 
						|
			encodeCanonical(key, result)
 | 
						|
 | 
						|
			result.WriteString(":")
 | 
						|
			if err := encodeCanonical(objAsserted[key], result); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if i < (len(mapKeys) - 1) {
 | 
						|
				result.WriteString(",")
 | 
						|
			}
 | 
						|
			i++
 | 
						|
		}
 | 
						|
		result.WriteString("}")
 | 
						|
 | 
						|
	default:
 | 
						|
		// We recover in a deferred function defined above
 | 
						|
		panic(fmt.Sprintf("Can't canonicalize '%s' of type '%s'",
 | 
						|
			objAsserted, reflect.TypeOf(objAsserted)))
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
EncodeCanonical JSON canonicalizes the passed object and returns it as a byte
 | 
						|
slice.  It uses the OLPC canonical JSON specification (see
 | 
						|
http://wiki.laptop.org/go/Canonical_JSON).  If canonicalization fails the byte
 | 
						|
slice is nil and the second return value contains the error.
 | 
						|
*/
 | 
						|
func EncodeCanonical(obj interface{}) ([]byte, error) {
 | 
						|
	// FIXME: Terrible hack to turn the passed struct into a map, converting
 | 
						|
	// the struct's variable names to the json key names defined in the struct
 | 
						|
	data, err := json.Marshal(obj)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var jsonMap interface{}
 | 
						|
 | 
						|
	dec := json.NewDecoder(bytes.NewReader(data))
 | 
						|
	dec.UseNumber()
 | 
						|
	if err := dec.Decode(&jsonMap); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Create a buffer and write the canonicalized JSON bytes to it
 | 
						|
	var result bytes.Buffer
 | 
						|
	if err := encodeCanonical(jsonMap, &result); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return result.Bytes(), nil
 | 
						|
}
 |