Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions broker/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ broker/**/*_test.go
# Include OpenAPI specs
!broker/oapi/*.yaml

# Include embedded built-in profiles
!broker/profiles/builtin/*.yaml

# Include statemodels
!broker/patron_request/service/statemodels

Expand Down
11 changes: 7 additions & 4 deletions broker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,20 @@ IMPORT_OAPI_CFG = $(IMPORT_OAPI_DIR)/import-cfg.yaml
IMPORT_OAPI_SPEC = $(IMPORT_OAPI_DIR)/open-api.yaml
IMPORT_OAPI_GEN = import/oapi/import_openapi_gen.go

.PHONY: all docker generate generate-sqlc generate-api generate-commit-id check run fmt fmt-check clean clean-build-caches view-coverage deps-update tools-update lint vulncheck check-coverage
.PHONY: all docker generate generate-sqlc generate-api generate-commit-id directory-generate check run fmt fmt-check clean clean-build-caches view-coverage deps-update tools-update lint vulncheck check-coverage

all: $(BINARY) archive

docker: generate
cd .. && $(DOCKER) build -t indexdata/$(MODULE):latest -f ./$(MODULE)/Dockerfile .

generate: generate-commit-id generate-sqlc generate-api $(STATE_MODELS_JSON)
generate: generate-commit-id generate-sqlc generate-api directory-generate $(STATE_MODELS_JSON)

generate-commit-id: $(COMMIT_ID)

directory-generate:
$(MAKE) -C ../directory generate

generate-sqlc: $(SQL_GEN_OUT)

generate-api: $(OAPI_GEN) $(PR_OAPI_GEN) $(PS_OAPI_GEN) $(SCHED_OAPI_GEN) $(IMPORT_OAPI_GEN)
Expand Down Expand Up @@ -109,10 +112,10 @@ $(SQL_GEN_OUT): $(SQL_GEN_IN) $(SQLC_CONFIG)
$(COMMIT_ID): $(GIT_COMMIT_DEPS)
commit_id="$$( $(GIT) rev-parse --short HEAD )" && printf '%s' "$$commit_id" > $(COMMIT_ID)

$(BINARY): $(COMMIT_ID) $(SQL_GEN_OUT) $(OAPI_GEN) $(PR_OAPI_GEN) $(PS_OAPI_GEN) $(SCHED_OAPI_GEN) $(IMPORT_OAPI_GEN) $(BUILD_GOFILES) $(STATE_MODELS_JSON)
$(BINARY): $(COMMIT_ID) $(SQL_GEN_OUT) $(OAPI_GEN) $(PR_OAPI_GEN) $(PS_OAPI_GEN) $(SCHED_OAPI_GEN) $(IMPORT_OAPI_GEN) $(BUILD_GOFILES) $(STATE_MODELS_JSON) ../directory/api.yaml | directory-generate
$(GO) build -v -o $(BINARY) ./$(MAIN_PACKAGE)

archive: $(COMMIT_ID) $(SQL_GEN_OUT) $(OAPI_GEN) $(PR_OAPI_GEN) $(PS_OAPI_GEN) $(SCHED_OAPI_GEN) $(IMPORT_OAPI_GEN) $(BUILD_GOFILES) $(STATE_MODELS_JSON)
archive: $(COMMIT_ID) $(SQL_GEN_OUT) $(OAPI_GEN) $(PR_OAPI_GEN) $(PS_OAPI_GEN) $(SCHED_OAPI_GEN) $(IMPORT_OAPI_GEN) $(BUILD_GOFILES) $(STATE_MODELS_JSON) ../directory/api.yaml | directory-generate
$(GO) build -v -o archive ./cmd/archive

check: generate
Expand Down
107 changes: 62 additions & 45 deletions broker/profiles/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,84 @@ package profiles

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"strings"
"maps"
"slices"
"sync"

dirapi "github.com/indexdata/crosslink/directory/api"
"gopkg.in/yaml.v3"
)

//go:embed builtin/*.yaml
var builtinFiles embed.FS

type builtinProfile struct {
LMS object `json:"lmsConfig"`
Catalog object `json:"catalogConfig"`
}

// Load once; merge copies nested maps before applying directory overrides.
var builtins = loadBuiltins()

func loadBuiltins() map[string]builtinProfile {
files, err := builtinFiles.ReadDir("builtin")
// Cache definitions and errors alike: the bundled specification cannot change
// during this process. Resolve must never modify these shared defaults.
var loadBuiltins = sync.OnceValues(func() (map[string]builtinProfile, error) {
data, err := dirapi.GetSpecJSON()
if err != nil {
panic(err)
return nil, fmt.Errorf("load bundled directory specification: %w", err)
}
result := map[string]builtinProfile{}
for _, file := range files {
data, err := builtinFiles.ReadFile("builtin/" + file.Name())
if err != nil {
panic(err)
}
profile, err := decodeBuiltin(data)
if err != nil {
panic(fmt.Errorf("built-in profile %s: %w", file.Name(), err))
}
result[strings.TrimSuffix(file.Name(), ".yaml")] = profile
}
return result
}
return decodeBuiltins(data)
})

func decodeBuiltin(data []byte) (builtinProfile, error) {
var raw object
if err := yaml.Unmarshal(data, &raw); err != nil {
return builtinProfile{}, err
func decodeBuiltins(data []byte) (map[string]builtinProfile, error) {
var schema struct {
Profiles map[string]map[string]json.RawMessage `json:"x-host-profiles"`
Components struct {
Schemas map[string]struct {
Enum []string `json:"enum"`
} `json:"schemas"`
} `json:"components"`
}
data, err := json.Marshal(raw)
if err != nil {
return builtinProfile{}, err
if err := json.Unmarshal(data, &schema); err != nil {
return nil, fmt.Errorf("decode directory specification: %w", err)
}
// Validate names and types against the directory API, including nested fields.
// Only host configuration sections belong in these profiles.
var config struct {
LMS *dirapi.LmsConfig `json:"lmsConfig"`
Catalog *dirapi.CatalogConfig `json:"catalogConfig"`
if len(schema.Profiles) == 0 {
return nil, fmt.Errorf("directory specification: missing or empty x-host-profiles")
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&config); err != nil {
return builtinProfile{}, err
for _, name := range []string{"Generic", "Alma", "Sierra", "Koha", "FOLIO"} {
if schema.Profiles[name] == nil {
return nil, fmt.Errorf("host profile %s: missing definition", name)
}
}
vendors := schema.Components.Schemas["HostLmsVendor"].Enum
profiles := make(map[string]builtinProfile, len(schema.Profiles))
for _, name := range slices.Sorted(maps.Keys(schema.Profiles)) {
if !slices.Contains(vendors, name) {
return nil, fmt.Errorf("host profile %s: not in HostLmsVendor enum", name)
}
raw := schema.Profiles[name]
// JSON struct decoding accepts case variants, but profile sections must
// use their canonical names, including when both spellings are present.
for _, section := range slices.Sorted(maps.Keys(raw)) {
if section != "lmsConfig" && section != "catalogConfig" {
return nil, fmt.Errorf("host profile %s: invalid section %q; allowed sections are lmsConfig and catalogConfig", name, section)
}
}
encoded, err := json.Marshal(raw)
if err != nil {
return nil, fmt.Errorf("host profile %s: encode definition: %w", name, err)
}
var config struct {
LMS *dirapi.LmsConfig `json:"lmsConfig"`
Catalog *dirapi.CatalogConfig `json:"catalogConfig"`
}
decoder := json.NewDecoder(bytes.NewReader(encoded))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&config); err != nil {
return nil, fmt.Errorf("host profile %s: validate definition: %w", name, err)
}
// Retain raw maps rather than re-encoding the typed configuration, which
// could materialize omitted fields or omit explicit zero values.
var profile builtinProfile
if err := json.Unmarshal(encoded, &profile); err != nil {
return nil, fmt.Errorf("host profile %s: decode definition: %w", name, err)
}
profiles[name] = profile
}
var profile builtinProfile
err = json.Unmarshal(data, &profile)
return profile, err
return profiles, nil
}
12 changes: 0 additions & 12 deletions broker/profiles/builtin/Alma.yaml

This file was deleted.

13 changes: 0 additions & 13 deletions broker/profiles/builtin/FOLIO.yaml

This file was deleted.

16 changes: 0 additions & 16 deletions broker/profiles/builtin/Generic.yaml

This file was deleted.

18 changes: 0 additions & 18 deletions broker/profiles/builtin/Koha.yaml

This file was deleted.

18 changes: 0 additions & 18 deletions broker/profiles/builtin/Sierra.yaml

This file was deleted.

17 changes: 0 additions & 17 deletions broker/profiles/builtin/profile.schema.json

This file was deleted.

Loading
Loading