Centralize builder request cloning
This commit is contained in:
@@ -51,11 +51,12 @@ until its producing step completes.
|
|||||||
|
|
||||||
Preparation is the construction boundary. It validates the resolved shape and
|
Preparation is the construction boundary. It validates the resolved shape and
|
||||||
registry set, clones the resolved data, then constructs the input adapter,
|
registry set, clones the resolved data, then constructs the input adapter,
|
||||||
chunker, stage-local validators, every typed lane, and output encoder with
|
chunker, stage-local validators, every typed lane, and output encoder. Each
|
||||||
cloned options, references, and shared dependencies. It also collects stable
|
registered builder receives its own cloned build request immediately before its
|
||||||
checkpoint fingerprints. Missing registrations, incompatible typed entries,
|
module-owned code runs. Preparation also collects stable checkpoint
|
||||||
nil implementations, and constructor failures are reported before source
|
fingerprints. Missing registrations, incompatible typed entries, nil
|
||||||
parsing or any stage operation begins.
|
implementations, and constructor failures are reported before source parsing
|
||||||
|
or any stage operation begins.
|
||||||
|
|
||||||
An output encoder can opt into source-evidence publication through its output
|
An output encoder can opt into source-evidence publication through its output
|
||||||
policy. Preparation keeps the configured lane allowlist and active lanes
|
policy. Preparation keeps the configured lane allowlist and active lanes
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ func TestPrepareDeliversTargetReferencesAsIndependentBuildInputs(t *testing.T) {
|
|||||||
if name != "extract" {
|
if name != "extract" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
request.Options["nested"].(map[string]any)["value"] = "mutated by extractor builder"
|
||||||
slot := request.References.Slots["extract"]
|
slot := request.References.Slots["extract"]
|
||||||
slot.Items[0].Content = []byte("mutated by extractor builder")
|
slot.Items[0].Content = []byte("mutated by extractor builder")
|
||||||
request.References.Slots["extract"] = slot
|
request.References.Slots["extract"] = slot
|
||||||
@@ -318,6 +319,7 @@ func TestPrepareDeliversTargetReferencesAsIndependentBuildInputs(t *testing.T) {
|
|||||||
resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet = constructionReferenceSet("extract", "extract reference")
|
resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet = constructionReferenceSet("extract", "extract reference")
|
||||||
resolved.Steps[0].ArtifactLanes[0].MergeReferences.ReferenceSet = constructionReferenceSet("merge", "merge reference")
|
resolved.Steps[0].ArtifactLanes[0].MergeReferences.ReferenceSet = constructionReferenceSet("merge", "merge reference")
|
||||||
resolved.Steps[0].ArtifactLanes[0].NormalizeReferences.ReferenceSet = constructionReferenceSet("normalize", "normalize reference")
|
resolved.Steps[0].ArtifactLanes[0].NormalizeReferences.ReferenceSet = constructionReferenceSet("normalize", "normalize reference")
|
||||||
|
resolved.Steps[0].ArtifactLanes[0].Extract.Options = constructionBuildRequest().Options
|
||||||
|
|
||||||
prepared, err := Prepare(resolved, registries, ModuleDependencies{})
|
prepared, err := Prepare(resolved, registries, ModuleDependencies{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -339,6 +341,15 @@ func TestPrepareDeliversTargetReferencesAsIndependentBuildInputs(t *testing.T) {
|
|||||||
if got := constructionReferenceContent(resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet); got != "extract reference" {
|
if got := constructionReferenceContent(resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet); got != "extract reference" {
|
||||||
t.Fatalf("resolved extract references = %q, want original content", got)
|
t.Fatalf("resolved extract references = %q, want original content", got)
|
||||||
}
|
}
|
||||||
|
if got := constructionReferenceContent(prepared.resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet); got != "extract reference" {
|
||||||
|
t.Fatalf("prepared extract references = %q, want original content", got)
|
||||||
|
}
|
||||||
|
if got := resolved.Steps[0].ArtifactLanes[0].Extract.Options["nested"].(map[string]any)["value"]; got != "original" {
|
||||||
|
t.Fatalf("resolved extract options = %#v, want original nested value", got)
|
||||||
|
}
|
||||||
|
if got := prepared.resolved.Steps[0].ArtifactLanes[0].Extract.Options["nested"].(map[string]any)["value"]; got != "original" {
|
||||||
|
t.Fatalf("prepared extract options = %#v, want original nested value", got)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = prepared.Steps[0].lanes[0].typed.extract(context.Background(), prepared.Steps[0].lanes[0].typed.extractor, contracts.TypedExtractionRequest{
|
_, err = prepared.Steps[0].lanes[0].typed.extract(context.Background(), prepared.Steps[0].lanes[0].typed.extractor, contracts.TypedExtractionRequest{
|
||||||
References: CloneReferenceSet(resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet),
|
References: CloneReferenceSet(resolved.Steps[0].ArtifactLanes[0].ExtractReferences.ReferenceSet),
|
||||||
@@ -354,6 +365,163 @@ func TestPrepareDeliversTargetReferencesAsIndependentBuildInputs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisteredBuildersReceiveIndependentBuildRequests(t *testing.T) {
|
||||||
|
request := constructionBuildRequest()
|
||||||
|
probe := buildRequestMutationProbe{t: t, want: cloneBuildRequest(request)}
|
||||||
|
|
||||||
|
inputs := NewInputAdapterRegistry()
|
||||||
|
if err := inputs.RegisterBuilderWithSpec(testModuleSpec("input", StageInput), rejectUnconfiguredOptions, func(request BuildRequest) (contracts.InputAdapter, error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return &constructionInput{key: "input"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
chunkers := NewChunkerRegistry()
|
||||||
|
if err := chunkers.RegisterBuilderWithSpec(testModuleSpec("chunk", StageChunk), rejectUnconfiguredOptions, func(request BuildRequest) (contracts.Chunker, error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return &typedTestChunker{key: "chunk"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
outputs := NewOutputEncoderRegistry()
|
||||||
|
if err := outputs.RegisterBuilderWithSpec(testModuleSpec("output", StageOutput), rejectUnconfiguredOptions, func(request BuildRequest) (contracts.OutputEncoder, error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return &typedTestOutput{key: "output"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
extractors := NewExtractorRegistry()
|
||||||
|
extractSpec := testModuleSpec("extract", StageExtract)
|
||||||
|
extractSpec.ArtifactKind = "test/notes"
|
||||||
|
if err := RegisterExtractorBuilder(extractors, extractSpec, rejectUnconfiguredOptions, func(request BuildRequest) (contracts.Extractor[codecNotes], error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return typedTestExtractor[codecNotes]{key: "extract"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mergers := NewMergerRegistry()
|
||||||
|
mergeSpec := testModuleSpec("merge", StageMerge)
|
||||||
|
mergeSpec.ArtifactKind = "test/notes"
|
||||||
|
if err := RegisterMergerBuilder(mergers, mergeSpec, rejectUnconfiguredOptions, func(request BuildRequest) (contracts.Merger[codecNotes], error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return typedTestMerger[codecNotes]{key: "merge"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
normalizers := NewNormalizerRegistry()
|
||||||
|
normalizeSpec := testModuleSpec("normalize", StageNormalize)
|
||||||
|
normalizeSpec.ArtifactKind = "test/notes"
|
||||||
|
if err := RegisterNormalizerBuilder(normalizers, normalizeSpec, rejectUnconfiguredOptions, func(request BuildRequest) (contracts.Normalizer[codecNotes], error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return typedTestNormalizer[codecNotes]{key: "normalize"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
validators := NewValidatorRegistry()
|
||||||
|
if err := RegisterChunkValidatorBuilder(validators, ValidatorSpec{Key: "chunk-validator", ExecutionClass: contracts.ExecutionClassDeterministic}, rejectUnconfiguredOptions, func(request BuildRequest) (contracts.ChunkValidator, error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return typedTestChunkValidator{key: "chunk-validator"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := RegisterTypedValidatorBuilder(validators, "test/notes", ValidatorSpec{Key: "typed-validator", ExecutionClass: contracts.ExecutionClassDeterministic}, rejectUnconfiguredOptions, func(request BuildRequest) (contracts.TypedValidator[codecNotes], error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return typedTestValidator[codecNotes]{key: "typed-validator"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := RegisterSerializedValidatorBuilder(validators, SerializedValidatorSpec{ValidatorSpec: ValidatorSpec{Key: "serialized-validator", ExecutionClass: contracts.ExecutionClassDeterministic}, SupportsArtifacts: true}, rejectUnconfiguredOptions, func(request BuildRequest) (contracts.SerializedValidator, error) {
|
||||||
|
probe.observe(request)
|
||||||
|
return typedTestSerializedValidator{key: "serialized-validator"}, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
builders := []struct {
|
||||||
|
name string
|
||||||
|
call func() error
|
||||||
|
}{
|
||||||
|
{name: "input", call: func() error { _, err := inputs.BuildWithRequest("input", request); return err }},
|
||||||
|
{name: "chunk", call: func() error { _, err := chunkers.BuildWithRequest("chunk", request); return err }},
|
||||||
|
{name: "output", call: func() error { _, err := outputs.BuildWithRequest("output", request); return err }},
|
||||||
|
{name: "extract", call: func() error {
|
||||||
|
entry, _ := extractors.typedEntry("extract")
|
||||||
|
_, err := entry.builder(request)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
{name: "merge", call: func() error {
|
||||||
|
entry, _ := mergers.typedEntry("merge", "test/notes")
|
||||||
|
_, err := entry.builder(request)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
{name: "normalize", call: func() error {
|
||||||
|
entry, _ := normalizers.typedEntry("normalize", "test/notes")
|
||||||
|
_, err := entry.builder(request)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
{name: "chunk validator", call: func() error {
|
||||||
|
entry, _ := validators.chunkEntry("chunk-validator")
|
||||||
|
_, err := entry.builder(request)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
{name: "typed validator", call: func() error {
|
||||||
|
entry, _ := validators.typedEntry("typed-validator", "test/notes")
|
||||||
|
_, err := entry.builder(request)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
{name: "serialized validator", call: func() error {
|
||||||
|
entry, _ := validators.serializedEntry("serialized-validator")
|
||||||
|
_, err := entry.builder(request)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, builder := range builders {
|
||||||
|
t.Run(builder.name, func(t *testing.T) {
|
||||||
|
if err := builder.call(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(request, probe.want) {
|
||||||
|
t.Fatalf("build request = %#v, want unchanged %#v", request, probe.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildRequestMutationProbe struct {
|
||||||
|
t *testing.T
|
||||||
|
want BuildRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (probe buildRequestMutationProbe) observe(request BuildRequest) {
|
||||||
|
probe.t.Helper()
|
||||||
|
if !reflect.DeepEqual(request, probe.want) {
|
||||||
|
probe.t.Fatalf("builder request = %#v, want independently owned %#v", request, probe.want)
|
||||||
|
}
|
||||||
|
options := request.Options["nested"].(map[string]any)
|
||||||
|
options["value"] = "mutated"
|
||||||
|
request.Options["items"].([]any)[0].(map[string]any)["value"] = "mutated"
|
||||||
|
request.Options["bytes"].([]byte)[0] = 'x'
|
||||||
|
slot := request.References.Slots["reference"]
|
||||||
|
slot.Items[0].Content[0] = 'x'
|
||||||
|
slot.Items = append(slot.Items, contracts.ReferenceItem{SlotName: "reference", Content: []byte("extra")})
|
||||||
|
request.References.Slots["reference"] = slot
|
||||||
|
delete(request.References.Slots, "unused")
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructionBuildRequest() BuildRequest {
|
||||||
|
return BuildRequest{
|
||||||
|
Options: map[string]any{
|
||||||
|
"nested": map[string]any{"value": "original"},
|
||||||
|
"items": []any{map[string]any{"value": "original"}},
|
||||||
|
"bytes": []byte("original"),
|
||||||
|
},
|
||||||
|
References: contracts.ReferenceSet{Slots: map[string]contracts.ResolvedReferenceSlot{
|
||||||
|
"reference": {Slot: contracts.ReferenceSlot{Name: "reference"}, Items: []contracts.ReferenceItem{{SlotName: "reference", Content: []byte("original")}}},
|
||||||
|
"unused": {Slot: contracts.ReferenceSlot{Name: "unused"}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrepareFailuresOccurBeforeInputParse(t *testing.T) {
|
func TestPrepareFailuresOccurBeforeInputParse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func Prepare(resolved ResolvedPipeline, registries Registries, deps ModuleDepend
|
|||||||
artifactCodecs: registries.ArtifactCodecs,
|
artifactCodecs: registries.ArtifactCodecs,
|
||||||
}
|
}
|
||||||
request := func(binding ModuleBinding, references contracts.ReferenceSet) BuildRequest {
|
request := func(binding ModuleBinding, references contracts.ReferenceSet) BuildRequest {
|
||||||
return BuildRequest{Dependencies: deps, Options: cloneOptions(binding.Options), References: references}
|
return BuildRequest{Dependencies: deps, Options: binding.Options, References: references}
|
||||||
}
|
}
|
||||||
|
|
||||||
input, err := registries.Inputs.BuildWithRequest(stable.Input.Module, request(stable.Input, contracts.ReferenceSet{}))
|
input, err := registries.Inputs.BuildWithRequest(stable.Input.Module, request(stable.Input, contracts.ReferenceSet{}))
|
||||||
@@ -202,7 +202,7 @@ func prepareEvidencePlan(resolved ResolvedPipeline, registries Registries, outpu
|
|||||||
func prepareLane(pipeline ResolvedPipeline, lane ResolvedArtifactLane, registries Registries, deps ModuleDependencies) (preparedLaneExecutor, error) {
|
func prepareLane(pipeline ResolvedPipeline, lane ResolvedArtifactLane, registries Registries, deps ModuleDependencies) (preparedLaneExecutor, error) {
|
||||||
executor := preparedLaneExecutor{resolved: cloneResolvedArtifactLane(lane)}
|
executor := preparedLaneExecutor{resolved: cloneResolvedArtifactLane(lane)}
|
||||||
request := func(binding ModuleBinding, references contracts.ReferenceSet) BuildRequest {
|
request := func(binding ModuleBinding, references contracts.ReferenceSet) BuildRequest {
|
||||||
return BuildRequest{Dependencies: deps, Options: cloneOptions(binding.Options), References: references}
|
return BuildRequest{Dependencies: deps, Options: binding.Options, References: references}
|
||||||
}
|
}
|
||||||
extractEntry, ok := registries.Extractors.typedEntry(lane.Extract.Module)
|
extractEntry, ok := registries.Extractors.typedEntry(lane.Extract.Module)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -259,7 +259,7 @@ func prepareValidatorChain(pipeline ResolvedPipeline, registries Registries, dep
|
|||||||
resolved := resolvedValidatorChain(stage, laneID, moduleKey, pipeline.ValidatorChains)
|
resolved := resolvedValidatorChain(stage, laneID, moduleKey, pipeline.ValidatorChains)
|
||||||
prepared := preparedValidatorChain{resolved: resolved}
|
prepared := preparedValidatorChain{resolved: resolved}
|
||||||
for _, validator := range resolved.Validators {
|
for _, validator := range resolved.Validators {
|
||||||
request := BuildRequest{Dependencies: deps, Options: cloneOptions(validator.Binding.Options), References: references}
|
request := BuildRequest{Dependencies: deps, Options: validator.Binding.Options, References: references}
|
||||||
built, err := buildPreparedValidator(registries.Validators, validator, request)
|
built, err := buildPreparedValidator(registries.Validators, validator, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return preparedValidatorChain{}, constructionError(pipeline.ID, laneID, stage, moduleKey, validator.Binding.Module, err)
|
return preparedValidatorChain{}, constructionError(pipeline.ID, laneID, stage, moduleKey, validator.Binding.Module, err)
|
||||||
@@ -280,7 +280,7 @@ func buildPreparedValidator(registry *ValidatorRegistry, resolved ResolvedValida
|
|||||||
if !ok {
|
if !ok {
|
||||||
return preparedValidator{}, fmt.Errorf("typed construction entry is not registered")
|
return preparedValidator{}, fmt.Errorf("typed construction entry is not registered")
|
||||||
}
|
}
|
||||||
implementation, err = entry.builder(cloneBuildRequest(request))
|
implementation, err = entry.builder(request)
|
||||||
prepared.typed = implementation
|
prepared.typed = implementation
|
||||||
prepared.typedValidate = entry.validate
|
prepared.typedValidate = entry.validate
|
||||||
case ValidatorTargetChunk:
|
case ValidatorTargetChunk:
|
||||||
@@ -288,14 +288,14 @@ func buildPreparedValidator(registry *ValidatorRegistry, resolved ResolvedValida
|
|||||||
if !ok {
|
if !ok {
|
||||||
return preparedValidator{}, fmt.Errorf("chunk construction entry is not registered")
|
return preparedValidator{}, fmt.Errorf("chunk construction entry is not registered")
|
||||||
}
|
}
|
||||||
prepared.chunk, err = entry.builder(cloneBuildRequest(request))
|
prepared.chunk, err = entry.builder(request)
|
||||||
implementation = prepared.chunk
|
implementation = prepared.chunk
|
||||||
case ValidatorTargetSerialized:
|
case ValidatorTargetSerialized:
|
||||||
entry, ok := registry.serializedEntry(key)
|
entry, ok := registry.serializedEntry(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return preparedValidator{}, fmt.Errorf("serialized construction entry is not registered")
|
return preparedValidator{}, fmt.Errorf("serialized construction entry is not registered")
|
||||||
}
|
}
|
||||||
prepared.serialized, err = entry.builder(cloneBuildRequest(request))
|
prepared.serialized, err = entry.builder(request)
|
||||||
implementation = prepared.serialized
|
implementation = prepared.serialized
|
||||||
default:
|
default:
|
||||||
return preparedValidator{}, fmt.Errorf("validator construction target %q is not supported", resolved.Target)
|
return preparedValidator{}, fmt.Errorf("validator construction target %q is not supported", resolved.Target)
|
||||||
@@ -323,7 +323,7 @@ func buildPreparedValidator(registry *ValidatorRegistry, resolved ResolvedValida
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildErasedModule(builder func(BuildRequest) (any, error), request BuildRequest, key, kind string) (any, error) {
|
func buildErasedModule(builder func(BuildRequest) (any, error), request BuildRequest, key, kind string) (any, error) {
|
||||||
implementation, err := builder(cloneBuildRequest(request))
|
implementation, err := builder(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,13 @@ func RegisterChunkValidatorBuilder(registry *ValidatorRegistry, spec ValidatorSp
|
|||||||
if registry.chunkEntries == nil {
|
if registry.chunkEntries == nil {
|
||||||
registry.chunkEntries = make(map[string]chunkValidatorEntry)
|
registry.chunkEntries = make(map[string]chunkValidatorEntry)
|
||||||
}
|
}
|
||||||
registry.chunkEntries[normalizedSpec.Key] = chunkValidatorEntry{spec: normalizedSpec, validateOptions: validateOptions, builder: builder}
|
registry.chunkEntries[normalizedSpec.Key] = chunkValidatorEntry{
|
||||||
|
spec: normalizedSpec,
|
||||||
|
validateOptions: validateOptions,
|
||||||
|
builder: func(request BuildRequest) (contracts.ChunkValidator, error) {
|
||||||
|
return builder(cloneBuildRequest(request))
|
||||||
|
},
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +193,13 @@ func RegisterSerializedValidatorBuilder(registry *ValidatorRegistry, spec Serial
|
|||||||
if registry.serializedEntries == nil {
|
if registry.serializedEntries == nil {
|
||||||
registry.serializedEntries = make(map[string]serializedValidatorEntry)
|
registry.serializedEntries = make(map[string]serializedValidatorEntry)
|
||||||
}
|
}
|
||||||
registry.serializedEntries[spec.Key] = serializedValidatorEntry{spec: spec, validateOptions: validateOptions, builder: builder}
|
registry.serializedEntries[spec.Key] = serializedValidatorEntry{
|
||||||
|
spec: spec,
|
||||||
|
validateOptions: validateOptions,
|
||||||
|
builder: func(request BuildRequest) (contracts.SerializedValidator, error) {
|
||||||
|
return builder(cloneBuildRequest(request))
|
||||||
|
},
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,16 +50,15 @@ func (c EffectiveCatalog) Lookup(name string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveEffectiveCatalog loads the embedded SRD catalog and applies the
|
// ResolveEffectiveCatalog loads the embedded SRD catalog and applies the
|
||||||
// optional spell-catalog overlay found in the cloned reference set. It does
|
// optional spell-catalog overlay found in its spell_catalog reference slot. It
|
||||||
// not resolve paths or perform filesystem access.
|
// does not resolve paths or perform filesystem access.
|
||||||
func ResolveEffectiveCatalog(references contracts.ReferenceSet) (EffectiveCatalog, error) {
|
func ResolveEffectiveCatalog(references contracts.ReferenceSet) (EffectiveCatalog, error) {
|
||||||
base, err := LoadSRD5E2014()
|
base, err := LoadSRD5E2014()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return EffectiveCatalog{}, err
|
return EffectiveCatalog{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cloned := cloneReferenceSet(references)
|
slot, ok := references.Slots[SpellCatalogReferenceSlot]
|
||||||
slot, ok := cloned.Slots[SpellCatalogReferenceSlot]
|
|
||||||
if !ok || len(slot.Items) == 0 {
|
if !ok || len(slot.Items) == 0 {
|
||||||
return composeEffectiveCatalog(base, nil)
|
return composeEffectiveCatalog(base, nil)
|
||||||
}
|
}
|
||||||
@@ -407,21 +406,3 @@ func cloneStringMap(values map[string]string) map[string]string {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneReferenceSet(in contracts.ReferenceSet) contracts.ReferenceSet {
|
|
||||||
if len(in.Slots) == 0 {
|
|
||||||
return contracts.ReferenceSet{}
|
|
||||||
}
|
|
||||||
out := contracts.ReferenceSet{Slots: make(map[string]contracts.ResolvedReferenceSlot, len(in.Slots))}
|
|
||||||
for name, slot := range in.Slots {
|
|
||||||
slot.Slot.AcceptedMediaTypes = append([]string(nil), slot.Slot.AcceptedMediaTypes...)
|
|
||||||
items := make([]contracts.ReferenceItem, len(slot.Items))
|
|
||||||
for index, item := range slot.Items {
|
|
||||||
item.Content = append([]byte(nil), item.Content...)
|
|
||||||
items[index] = item
|
|
||||||
}
|
|
||||||
slot.Items = items
|
|
||||||
out.Slots[name] = slot
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user