2017-02-25 16:25:33 +01:00
|
|
|
// Copyright 2016 Pulumi, Inc. All rights reserved.
|
2017-02-22 03:31:43 +01:00
|
|
|
|
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
2017-02-23 02:33:08 +01:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2017-02-22 23:32:03 +01:00
|
|
|
"reflect"
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
"github.com/pulumi/coconut/pkg/compiler/core"
|
|
|
|
"github.com/pulumi/coconut/pkg/tokens"
|
|
|
|
"github.com/pulumi/coconut/pkg/util/contract"
|
2017-02-22 03:31:43 +01:00
|
|
|
)
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// SerializedSnapshot is a serializable, flattened CocoGL graph structure, specifically for snapshots. It is similar
|
|
|
|
// to the actual Snapshot interface, except that it flattens and rearranges a few data structures for serializability.
|
|
|
|
type SerializedSnapshot struct {
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
Husk tokens.QName `json:"husk"` // the target environment name.
|
2017-02-25 16:25:33 +01:00
|
|
|
Package tokens.PackageName `json:"package"` // the package which created this graph.
|
|
|
|
Args *core.Args `json:"args,omitempty"` // the blueprint args for graph creation.
|
|
|
|
Refs *string `json:"refs,omitempty"` // the ref alias, if any (`#ref` by default).
|
|
|
|
Resources *SerializedResourceMap `json:"resources,omitempty"` // a map of monikers to resource vertices.
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-22 23:32:03 +01:00
|
|
|
// DefaultSnapshotReftag is the default ref tag for intra-graph edges.
|
|
|
|
const DefaultSnapshotReftag = "#ref"
|
2017-02-22 03:31:43 +01:00
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// SerializedResource is a serializable vertex within a CocoGL graph, specifically for resource snapshots.
|
|
|
|
type SerializedResource struct {
|
|
|
|
ID *ID `json:"id,omitempty"` // the provider ID for this resource, if any.
|
|
|
|
Type tokens.Type `json:"type"` // this resource's full type token.
|
|
|
|
Properties *SerializedPropertyMap `json:"properties,omitempty"` // an untyped bag of properties.
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// SerializedPropertyMap is a property map from resource key to the underlying property value.
|
|
|
|
type SerializedPropertyMap map[string]interface{}
|
2017-02-22 03:31:43 +01:00
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// SerializeSnapshot turns a snapshot into a CocoGL data structure suitable for serialization.
|
|
|
|
func SerializeSnapshot(snap Snapshot, reftag string) *SerializedSnapshot {
|
2017-02-22 03:31:43 +01:00
|
|
|
contract.Assert(snap != nil)
|
|
|
|
|
2017-02-22 23:32:03 +01:00
|
|
|
// Initialize the reftag if needed, and only serialize it if overridden.
|
2017-02-22 03:31:43 +01:00
|
|
|
var refp *string
|
|
|
|
if reftag == "" {
|
2017-02-22 23:32:03 +01:00
|
|
|
reftag = DefaultSnapshotReftag
|
2017-02-22 03:31:43 +01:00
|
|
|
} else {
|
|
|
|
refp = &reftag
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serialize all vertices and only include a vertex section if non-empty.
|
2017-02-25 16:25:33 +01:00
|
|
|
var resm *SerializedResourceMap
|
2017-02-23 02:33:08 +01:00
|
|
|
if snapres := snap.Resources(); len(snapres) > 0 {
|
2017-02-25 16:25:33 +01:00
|
|
|
resm = NewSerializedResourceMap()
|
2017-02-23 02:33:08 +01:00
|
|
|
for _, res := range snap.Resources() {
|
|
|
|
m := res.Moniker()
|
|
|
|
contract.Assertf(string(m) != "", "Unexpected empty resource moniker")
|
|
|
|
contract.Assertf(!resm.Has(m), "Unexpected duplicate resource moniker '%v'", m)
|
|
|
|
resm.Add(m, SerializeResource(res, reftag))
|
|
|
|
}
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only include the arguments in the output if non-emtpy.
|
|
|
|
var argsp *core.Args
|
|
|
|
if args := snap.Args(); len(args) > 0 {
|
|
|
|
argsp = &args
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
return &SerializedSnapshot{
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
Husk: snap.Husk(),
|
2017-02-22 23:32:03 +01:00
|
|
|
Package: snap.Pkg(), // TODO: eventually, this should carry version metadata too.
|
|
|
|
Args: argsp,
|
|
|
|
Refs: refp,
|
2017-02-23 02:33:08 +01:00
|
|
|
Resources: resm,
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// SerializeResource turns a resource into a CocoGL data structure suitable for serialization.
|
|
|
|
func SerializeResource(res Resource, reftag string) *SerializedResource {
|
2017-02-22 03:31:43 +01:00
|
|
|
contract.Assert(res != nil)
|
|
|
|
|
|
|
|
// Only serialize the ID if it is non-empty.
|
|
|
|
var idp *ID
|
|
|
|
if id := res.ID(); id != ID("") {
|
|
|
|
idp = &id
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serialize all properties recursively, and add them if non-empty.
|
2017-02-25 16:25:33 +01:00
|
|
|
var props *SerializedPropertyMap
|
2017-02-22 23:32:03 +01:00
|
|
|
if result, use := SerializeProperties(res.Properties(), reftag); use {
|
|
|
|
props = &result
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
return &SerializedResource{
|
2017-02-22 03:31:43 +01:00
|
|
|
ID: idp,
|
|
|
|
Type: res.Type(),
|
|
|
|
Properties: props,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 23:32:03 +01:00
|
|
|
// SerializeProperties serializes a resource property bag so that it's suitable for serialization.
|
2017-02-25 16:25:33 +01:00
|
|
|
func SerializeProperties(props PropertyMap, reftag string) (SerializedPropertyMap, bool) {
|
|
|
|
dst := make(SerializedPropertyMap)
|
2017-02-22 23:32:03 +01:00
|
|
|
for _, k := range StablePropertyKeys(props) {
|
|
|
|
if v, use := SerializeProperty(props[k], reftag); use {
|
|
|
|
dst[string(k)] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(dst) > 0 {
|
|
|
|
return dst, true
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-02-22 03:31:43 +01:00
|
|
|
// SerializeProperty serializes a resource property value so that it's suitable for serialization.
|
|
|
|
func SerializeProperty(prop PropertyValue, reftag string) (interface{}, bool) {
|
|
|
|
// Skip nulls.
|
|
|
|
if prop.IsNull() {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// For arrays, make sure to recurse.
|
|
|
|
if prop.IsArray() {
|
|
|
|
var arr []interface{}
|
|
|
|
for _, elem := range prop.ArrayValue() {
|
|
|
|
if v, use := SerializeProperty(elem, reftag); use {
|
|
|
|
arr = append(arr, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(arr) > 0 {
|
|
|
|
return arr, true
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also for objects, recurse and use naked properties.
|
|
|
|
if prop.IsObject() {
|
2017-02-22 23:32:03 +01:00
|
|
|
return SerializeProperties(prop.ObjectValue(), reftag)
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Morph resources into their equivalent `{ "#ref": "<moniker>" }` form.
|
|
|
|
if prop.IsResource() {
|
|
|
|
return map[string]string{
|
|
|
|
reftag: string(prop.ResourceValue()),
|
|
|
|
}, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// All others are returned as-is.
|
|
|
|
return prop.V, true
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// DeserializeSnapshot takes a serialized CocoGL snapshot data structure and returns its associated snapshot.
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
func DeserializeSnapshot(ctx *Context, ser *SerializedSnapshot) Snapshot {
|
2017-02-22 23:32:03 +01:00
|
|
|
// Determine the reftag to use.
|
|
|
|
var reftag string
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
if ser.Refs == nil {
|
2017-02-22 23:32:03 +01:00
|
|
|
reftag = DefaultSnapshotReftag
|
|
|
|
} else {
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
reftag = *ser.Refs
|
2017-02-22 23:32:03 +01:00
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// For every serialized resource vertex, create a SerializedResource out of it.
|
2017-02-22 23:32:03 +01:00
|
|
|
var resources []Resource
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
if ser.Resources != nil {
|
2017-02-22 23:32:03 +01:00
|
|
|
// TODO: we need to enumerate resources in the specific order in which they were emitted.
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
for _, kvp := range ser.Resources.Iter() {
|
2017-02-22 23:32:03 +01:00
|
|
|
// Deserialize the resources, if they exist.
|
2017-02-23 02:33:08 +01:00
|
|
|
res := kvp.Value
|
2017-02-22 23:32:03 +01:00
|
|
|
var props PropertyMap
|
|
|
|
if res.Properties == nil {
|
|
|
|
props = make(PropertyMap)
|
|
|
|
} else {
|
|
|
|
props = DeserializeProperties(*res.Properties, reftag)
|
|
|
|
}
|
|
|
|
|
|
|
|
// And now just produce a resource object using the information available.
|
|
|
|
var id ID
|
|
|
|
if res.ID != nil {
|
|
|
|
id = *res.ID
|
|
|
|
}
|
2017-02-23 02:33:08 +01:00
|
|
|
|
|
|
|
resources = append(resources, NewResource(id, kvp.Key, res.Type, props))
|
2017-02-22 23:32:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var args core.Args
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
if ser.Args != nil {
|
|
|
|
args = *ser.Args
|
2017-02-22 23:32:03 +01:00
|
|
|
}
|
|
|
|
|
Add basic targeting capability
This change partially implements pulumi/coconut#94, by adding the
ability to name targets during creation and reuse those names during
deletion and update. This simplifies the management of deployment
records, checkpoints, and snapshots.
I've opted to call these things "husks" (perhaps going overboard with
joy after our recent renaming). The basic idea is that for any
executable Nut that will be deployed, you have a nutpack/ directory
whose layout looks roughly as follows:
nutpack/
bin/
Nutpack.json
... any other compiled artifacts ...
husks/
... one snapshot per husk ...
For example, if we had a stage and prod husk, we would have:
nutpack/
bin/...
husks/
prod.json
stage.json
In the prod.json and stage.json files, we'd have the most recent
deployment record for that environment. These would presumably get
checked in and versioned along with the overall Nut, so that we
can use Git history for rollbacks, etc.
The create, update, and delete commands look in the right place for
these files automatically, so you don't need to manually supply them.
2017-02-25 18:24:52 +01:00
|
|
|
return NewSnapshot(ctx, ser.Husk, ser.Package, args, resources)
|
2017-02-22 23:32:03 +01:00
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func DeserializeProperties(props SerializedPropertyMap, reftag string) PropertyMap {
|
2017-02-22 23:32:03 +01:00
|
|
|
result := make(PropertyMap)
|
|
|
|
for k, prop := range props {
|
|
|
|
result[PropertyKey(k)] = DeserializeProperty(prop, reftag)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeserializeProperty(v interface{}, reftag string) PropertyValue {
|
|
|
|
if v != nil {
|
|
|
|
switch w := v.(type) {
|
|
|
|
case bool:
|
|
|
|
return NewPropertyBool(w)
|
|
|
|
case float64:
|
|
|
|
return NewPropertyNumber(w)
|
|
|
|
case string:
|
|
|
|
return NewPropertyString(w)
|
|
|
|
case []interface{}:
|
|
|
|
var arr []PropertyValue
|
|
|
|
for _, elem := range w {
|
|
|
|
arr = append(arr, DeserializeProperty(elem, reftag))
|
|
|
|
}
|
|
|
|
return NewPropertyArray(arr)
|
|
|
|
case map[string]interface{}:
|
|
|
|
// If the map has a single entry and it is the reftag, this is a moniker.
|
|
|
|
if len(w) == 1 {
|
|
|
|
if tag, has := w[reftag]; has {
|
|
|
|
if tagstr, isstring := tag.(string); isstring {
|
|
|
|
return NewPropertyResource(Moniker(tagstr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, this is an arbitrary object value.
|
2017-02-25 16:25:33 +01:00
|
|
|
obj := DeserializeProperties(SerializedPropertyMap(w), reftag)
|
2017-02-22 23:32:03 +01:00
|
|
|
return NewPropertyObject(obj)
|
|
|
|
default:
|
|
|
|
contract.Failf("Unrecognized property type: %v", reflect.ValueOf(v))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NewPropertyNull()
|
2017-02-22 03:31:43 +01:00
|
|
|
}
|
2017-02-23 02:33:08 +01:00
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// SerializedResourceMap is a map of moniker to resource, that also preserves a stable order of its keys. This ensures
|
2017-02-23 02:33:08 +01:00
|
|
|
// enumerations are ordered deterministically, versus Go's built-in map type whose enumeration is randomized.
|
|
|
|
// Additionally, because of this stable ordering, marshaling to and from JSON also preserves the order of keys.
|
2017-02-25 16:25:33 +01:00
|
|
|
type SerializedResourceMap struct {
|
|
|
|
m map[Moniker]*SerializedResource
|
2017-02-23 02:33:08 +01:00
|
|
|
keys []Moniker
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func NewSerializedResourceMap() *SerializedResourceMap {
|
|
|
|
return &SerializedResourceMap{m: make(map[Moniker]*SerializedResource)}
|
2017-02-23 02:33:08 +01:00
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Keys() []Moniker { return m.keys }
|
|
|
|
func (m *SerializedResourceMap) Len() int { return len(m.keys) }
|
2017-02-23 02:33:08 +01:00
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Add(k Moniker, v *SerializedResource) {
|
2017-02-23 02:33:08 +01:00
|
|
|
_, has := m.m[k]
|
|
|
|
contract.Assertf(!has, "Unexpected duplicate key '%v' added to map")
|
|
|
|
m.m[k] = v
|
|
|
|
m.keys = append(m.keys, k)
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Delete(k Moniker) {
|
2017-02-23 02:33:08 +01:00
|
|
|
_, has := m.m[k]
|
|
|
|
contract.Assertf(has, "Unexpected delete of non-existent key key '%v'")
|
|
|
|
delete(m.m, k)
|
|
|
|
for i, ek := range m.keys {
|
|
|
|
if ek == k {
|
|
|
|
newk := m.keys[:i]
|
|
|
|
m.keys = append(newk, m.keys[i+1:]...)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
contract.Assertf(i != len(m.keys)-1, "Expected to find deleted key '%v' in map's keys")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Get(k Moniker) (*SerializedResource, bool) {
|
2017-02-23 02:33:08 +01:00
|
|
|
v, has := m.m[k]
|
|
|
|
return v, has
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Has(k Moniker) bool {
|
2017-02-23 02:33:08 +01:00
|
|
|
_, has := m.m[k]
|
|
|
|
return has
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Must(k Moniker) *SerializedResource {
|
2017-02-23 02:33:08 +01:00
|
|
|
v, has := m.m[k]
|
|
|
|
contract.Assertf(has, "Expected key '%v' to exist in this map", k)
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Set(k Moniker, v *SerializedResource) {
|
2017-02-23 02:33:08 +01:00
|
|
|
_, has := m.m[k]
|
|
|
|
contract.Assertf(has, "Expected key '%v' to exist in this map for setting an element", k)
|
|
|
|
m.m[k] = v
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) SetOrAdd(k Moniker, v *SerializedResource) {
|
2017-02-23 02:33:08 +01:00
|
|
|
if _, has := m.m[k]; has {
|
|
|
|
m.Set(k, v)
|
|
|
|
} else {
|
|
|
|
m.Add(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
type SerializedResourceKeyValue struct {
|
2017-02-23 02:33:08 +01:00
|
|
|
Key Moniker
|
2017-02-25 16:25:33 +01:00
|
|
|
Value *SerializedResource
|
2017-02-23 02:33:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Iter can be used to conveniently range over a map's contents stably.
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) Iter() []SerializedResourceKeyValue {
|
|
|
|
var kvps []SerializedResourceKeyValue
|
2017-02-23 02:33:08 +01:00
|
|
|
for _, k := range m.Keys() {
|
2017-02-25 16:25:33 +01:00
|
|
|
kvps = append(kvps, SerializedResourceKeyValue{k, m.Must(k)})
|
2017-02-23 02:33:08 +01:00
|
|
|
}
|
|
|
|
return kvps
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) MarshalJSON() ([]byte, error) {
|
2017-02-23 02:33:08 +01:00
|
|
|
var b bytes.Buffer
|
|
|
|
b.WriteString("{")
|
|
|
|
for i, k := range m.Keys() {
|
|
|
|
if i != 0 {
|
|
|
|
b.WriteString(",")
|
|
|
|
}
|
|
|
|
|
|
|
|
kb, err := json.Marshal(k)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
b.Write(kb)
|
|
|
|
|
|
|
|
b.WriteString(":")
|
|
|
|
|
|
|
|
vb, err := json.Marshal(m.Must(k))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
b.Write(vb)
|
|
|
|
}
|
|
|
|
b.WriteString("}")
|
|
|
|
return b.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
func (m *SerializedResourceMap) UnmarshalJSON(b []byte) error {
|
2017-02-23 02:33:08 +01:00
|
|
|
contract.Assert(m.m == nil)
|
2017-02-25 16:25:33 +01:00
|
|
|
m.m = make(map[Moniker]*SerializedResource)
|
2017-02-23 02:33:08 +01:00
|
|
|
|
|
|
|
// Do a pass and read keys and values in the right order.
|
|
|
|
rdr := bytes.NewReader(b)
|
|
|
|
dec := json.NewDecoder(rdr)
|
|
|
|
|
|
|
|
// First, eat the open object curly '{':
|
|
|
|
contract.Assert(dec.More())
|
|
|
|
opencurly, err := dec.Token()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
contract.Assert(opencurly.(json.Delim) == '{')
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// Parse out every resource key (Moniker) and element (*SerializedResource):
|
2017-02-23 02:33:08 +01:00
|
|
|
for dec.More() {
|
|
|
|
// See if we've reached the closing '}'; if yes, chew on it and break.
|
|
|
|
token, err := dec.Token()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if closecurly, isclose := token.(json.Delim); isclose {
|
|
|
|
contract.Assert(closecurly == '}')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
k := Moniker(token.(string))
|
|
|
|
contract.Assert(dec.More())
|
2017-02-25 16:25:33 +01:00
|
|
|
var v *SerializedResource
|
2017-02-23 02:33:08 +01:00
|
|
|
if err := dec.Decode(&v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
contract.Assert(!m.Has(k))
|
|
|
|
m.Add(k, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|