platform
This commit is contained in:
1 parent
e706f7d907
commit
21bcb63440
42 files changed
+5903
No files matched your search
@@ -0,0 +1,171 @@
|
||||
// Schema declarations and the DefaultSchema consensus shape.
|
||||
//
|
||||
// A Schema maps a producer's private source JSON to the platform-wide
|
||||
// Projection. Field names point at keys in the source map; Value coercions
|
||||
// happen in the Builder. A Schema is the unit of configuration per tenant;
|
||||
// the Registry (registry.go) carries the per-tenant map.
|
||||
package catalog
|
||||
|
||||
// UpdatedAtSource controls where the Projection's UpdatedAt comes from.
|
||||
//
|
||||
// - StampAtPublish — Builder sets UpdatedAt to time.Now().UnixMilli() at the
|
||||
// moment Build() runs. RESERVED as a deliberate-fallback for sources
|
||||
// that genuinely have no other version stamp. NOT a content-version
|
||||
// address: a full snapshot rebuild re-stamps every projection and
|
||||
// invalidates every consumer cache even when nothing changed. Use only
|
||||
// when the producer side has NO source mtime / hash to set.
|
||||
//
|
||||
// - UseSourceField — the production-correct choice. Builder parses
|
||||
// source[Schema.UpdatedAtField] as a Unix-millisecond integer (or
|
||||
// decimal-fraction-of-Unix-second if the source uses seconds — Apply
|
||||
// accepts that and converts). This is the content-version value;
|
||||
// rebuilds produce identical UpdatedAt when content is identical.
|
||||
//
|
||||
// Critical contract: UpdatedAt is the CONTENT VERSION, not the publish
|
||||
// timestamp. Consumers use it for snapshot + revalidate. If your schema
|
||||
// sets UpdatedAtSource=StampAtPublish, every snapshot rebuild churns the
|
||||
// version. Choose UseSourceField for any consumer-cache scenario.
|
||||
type UpdatedAtSource int
|
||||
|
||||
const (
|
||||
// StampAtPublish sets UpdatedAt at Build() time. DefaultSchema
|
||||
// deliberately does NOT use this; it's reserved for callers who know
|
||||
// they want publish-time semantics.
|
||||
StampAtPublish UpdatedAtSource = iota
|
||||
// UseSourceField reads the version from Schema.UpdatedAtField. This is
|
||||
// the contract for content-versioned snapshots.
|
||||
UseSourceField
|
||||
)
|
||||
|
||||
// Schema is the declarative per-tenant mapping from a producer's source JSON
|
||||
// to the published Projection. Zero value is NOT valid — always start from
|
||||
// DefaultSchema() and override what differs, so unset-but-required fields
|
||||
// surface immediately rather than silently emitting empty Projections.
|
||||
//
|
||||
// All "Field" strings refer to keys in the source map[string]any that the
|
||||
// producer hands the Builder. Empty Field means "this Projection carrier is
|
||||
// supplied by Fixed* / Default* / Build-time, not read from source".
|
||||
type Schema struct {
|
||||
// Tenant is the registry key this schema is registered under. Empty
|
||||
// means "default" — see Registry.
|
||||
Tenant string `json:"tenant,omitempty"`
|
||||
|
||||
// --- source field references (per carrier) ---
|
||||
SKUField string `json:"sku_field"`
|
||||
TitleField string `json:"title_field,omitempty"`
|
||||
PriceField string `json:"price_field"`
|
||||
CurrencyField string `json:"currency_field,omitempty"`
|
||||
TaxClassField string `json:"tax_class_field,omitempty"`
|
||||
InventoryTrackedF string `json:"inventory_tracked_field,omitempty"`
|
||||
DropShipF string `json:"drop_ship_field,omitempty"`
|
||||
CategoryField string `json:"category_field,omitempty"`
|
||||
BrandField string `json:"brand_field,omitempty"`
|
||||
ImageField string `json:"image_field,omitempty"`
|
||||
|
||||
// --- fixed / default values used when a source field is absent ---.
|
||||
FixedCurrency string `json:"fixed_currency,omitempty"`
|
||||
// DefaultTaxClass is what the Builder writes when the source has no
|
||||
// TaxClass field (e.g. producer doesn't classify). Empty means "leave
|
||||
// empty" — consumer's tax.Provider gets called with empty string.
|
||||
DefaultTaxClass string `json:"default_tax_class,omitempty"`
|
||||
|
||||
// --- derived-value controls ---
|
||||
// PriceScale: source value interpreted as MINOR units if ≤ 1 (pass
|
||||
// through; floats round half-away-from-zero, ints pass through exact),
|
||||
// MAJOR units if 100 (multiply by 100 to get øre), etc. Negative =
|
||||
// rejected. The Builder rounds half-away-from-zero on fractional
|
||||
// results so the integer Projection never silently truncates.
|
||||
PriceScale int `json:"price_scale"`
|
||||
// IDPrefix is prepended to the normalised ID. Use "sku:" for the
|
||||
// consensus convention; an empty prefix means raw SKU.
|
||||
IDPrefix string `json:"id_prefix,omitempty"`
|
||||
// UpdatedAtSource — see type doc. DefaultSchema uses UseSourceField and
|
||||
// pins UpdatedAtField="updated_at". Producers MUST supply an updated_at
|
||||
// value (Unix-ms int); Apply returns ErrProjectionBuild (wrapped) when
|
||||
// it's missing rather than silently stamp-now'ing and churning every
|
||||
// rebuild. StampAtPublish is RESERVED for sources with no other stamp.
|
||||
UpdatedAtSource UpdatedAtSource `json:"updated_at_source"`
|
||||
// UpdatedAtField is the SOURCE-side key (when UpdatedAtSource ==
|
||||
// UseSourceField) the Builder reads the version from.
|
||||
UpdatedAtField string `json:"updated_at_field,omitempty"`
|
||||
// TrueValues is the set of source values that map to InventoryTracked
|
||||
// / DropShip = true. Comparison is lower-cased. Defaults to
|
||||
// DefaultTrueValues below if left nil — never add "false" / "0" etc,
|
||||
// the Builder treats anything not in TrueValues as false.
|
||||
TrueValues []string `json:"true_values,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultSchema is the consensus schema: SKU under "sku", price under
|
||||
// "price_inc_vat" already in øre, currency from source "currency" or empty,
|
||||
// tax_class from "tax_class", inventory_tracked / drop_ship from
|
||||
// "inventory_tracked" / "drop_ship" bool fields. updated_at MUST come from
|
||||
// the source under the field name "updated_at" (Unix-ms) — Apply rejects
|
||||
// missing values loudly rather than silent stamp-now churn.
|
||||
//
|
||||
// Use this when a tenant has no override file; it is the safe default for
|
||||
// producers that already emit platform-shaped JSON.
|
||||
func DefaultSchema() Schema {
|
||||
return Schema{
|
||||
Tenant: "default",
|
||||
// source-side field names
|
||||
SKUField: "sku",
|
||||
TitleField: "title",
|
||||
PriceField: "price_inc_vat",
|
||||
CurrencyField: "currency",
|
||||
TaxClassField: "tax_class",
|
||||
InventoryTrackedF: "inventory_tracked",
|
||||
DropShipF: "drop_ship",
|
||||
CategoryField: "category",
|
||||
BrandField: "brand",
|
||||
ImageField: "image",
|
||||
UpdatedAtField: "updated_at",
|
||||
|
||||
FixedCurrency: "", // read from source if present
|
||||
DefaultTaxClass: "standard",
|
||||
|
||||
PriceScale: 1, // price_inc_vat already in minor units
|
||||
IDPrefix: "sku:",
|
||||
UpdatedAtSource: UseSourceField, // change to StampAtPublish only with intent
|
||||
TrueValues: DefaultTrueValues(),
|
||||
}
|
||||
}
|
||||
|
||||
// BygglagretSchema is an explicit preset for the Swedish building-supplies
|
||||
// tenant that ships inc-VAT float prices in a "PriceInclVAT" string field.
|
||||
//
|
||||
// CRITICAL: TaxClassField (VATClass) is a STRING CLASS IDENTIFIER, not a raw
|
||||
// percent. The producer MUST pre-classify numeric VAT codes ("25" →
|
||||
// "standard", "12" → "reduced-12", "6" → "reduced-6") before publishing.
|
||||
// The wire contract is class identifiers; platform/tax resolves them to
|
||||
// numeric rates out of band. Shipping "25" as the class would break that
|
||||
// resolution path on the consumer side.
|
||||
func BygglagretSchema() Schema {
|
||||
return Schema{
|
||||
Tenant: "bygglagret",
|
||||
SKUField: "ArticleNumber",
|
||||
TitleField: "Name",
|
||||
PriceField: "PriceInclVAT",
|
||||
CurrencyField: "", // fixed below
|
||||
TaxClassField: "VATClass",
|
||||
InventoryTrackedF: "StockTracked",
|
||||
DropShipF: "DropshipFlag",
|
||||
CategoryField: "CategoryPath",
|
||||
BrandField: "Brand",
|
||||
ImageField: "PrimaryImage",
|
||||
UpdatedAtField: "LastModifiedMs",
|
||||
|
||||
FixedCurrency: "SEK",
|
||||
DefaultTaxClass: "standard",
|
||||
|
||||
PriceScale: 100, // PriceInclVAT is in SEK; multiply for øre
|
||||
IDPrefix: "sku:",
|
||||
UpdatedAtSource: UseSourceField,
|
||||
TrueValues: DefaultTrueValues(),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultTrueValues is the consensus truthy-string set for bool-typed
|
||||
// source fields. Lower-cased comparison in Builder.
|
||||
func DefaultTrueValues() []string {
|
||||
return []string{"true", "yes", "1", "on"}
|
||||
}
|
||||
Reference in new issue
Block a user