From fc8ecd868c383f9c3c058763ba3695b8f11a49e8 Mon Sep 17 00:00:00 2001 From: Hugo Wetterberg Date: Thu, 12 Feb 2026 15:11:20 +0100 Subject: [PATCH 1/3] add support for variant types that use the same schema as the base --- .github/workflows/lint.yaml | 4 +- .golangci.yml | 116 +++++++------- cmd/revisor/main.go | 1 + collection_test.go | 16 +- deprecation_test.go | 18 +-- document_constraint.go | 49 +++++- string_constraint.go | 5 +- testdata/example-article-template.json | 133 +++++++++++++++++ .../example-example-article-template.json | 1 + validation.go | 20 ++- validation_test.go | 141 +++++++++++++++++- 11 files changed, 418 insertions(+), 86 deletions(-) create mode 100644 testdata/example-article-template.json create mode 100644 testdata/results/example-example-article-template.json diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 9af4d1b..a1af281 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -17,7 +17,7 @@ jobs: go-version-file: go.mod cache: false # Let golangcilint handle caching - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 with: - version: v1.61 + version: v2.8 args: --timeout=4m diff --git a/.golangci.yml b/.golangci.yml index 594a3f9..341188b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,54 +1,66 @@ +version: "2" linters: enable: - - bodyclose - - copyloopvar - - dogsled - - dupl - - errcheck - - errorlint - - exhaustive - - forbidigo - - gci - - gochecknoinits - - goconst - - gocritic - - godot - - godox - - gofumpt - - goheader - - goimports - - gomoddirectives - - gomodguard - - goprintffuncname - - gosec - - gosimple - - govet - - importas - - ineffassign - - lll - - makezero - - misspell - - nakedret - - nestif - - nilnil - - nolintlint - - predeclared - - promlinter - - revive - - staticcheck - - stylecheck - - tenv - - testpackage - - thelper - - typecheck - - unconvert - - unparam - - unused - - whitespace - - wrapcheck - - wsl -linters-settings: - godox: - keywords: - - FIXME - - BUG + - bodyclose + - copyloopvar + - dogsled + - dupl + - errorlint + - exhaustive + - forbidigo + - gochecknoinits + - goconst + - gocritic + - godot + - godox + - goheader + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - importas + - lll + - makezero + - misspell + - nakedret + - nestif + - nilnil + - nolintlint + - predeclared + - promlinter + - revive + - staticcheck + - testpackage + - thelper + - unconvert + - unparam + - whitespace + - wrapcheck + - wsl_v5 + settings: + godox: + keywords: + - FIXME + - BUG + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/cmd/revisor/main.go b/cmd/revisor/main.go index 2f4bf3e..0cf42e0 100644 --- a/cmd/revisor/main.go +++ b/cmd/revisor/main.go @@ -37,6 +37,7 @@ func main() { if err := app.Run(os.Args); err != nil { _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) } } diff --git a/collection_test.go b/collection_test.go index 5543d68..e31455e 100644 --- a/collection_test.go +++ b/collection_test.go @@ -26,7 +26,7 @@ func TestCollection(t *testing.T) { ) testValidator, err := revisor.NewValidator(testConstraints...) - must(t, err, "failed to create test validator") + mustf(t, err, "failed to create test validator") tests := []validatorTest{ { @@ -37,7 +37,7 @@ func TestCollection(t *testing.T) { } paths, err := filepath.Glob(filepath.Join("testdata", "results-collection", "*.json")) - must(t, err, "failed to glob for collection result files") + mustf(t, err, "failed to glob for collection result files") for j := range tests { testCase := tests[j] @@ -75,7 +75,7 @@ func testCollectionAgainstGolden( var document newsdoc.Document // want []revisor.ValidationResult err := internal.UnmarshalFile(sourceDocPath, &document) - must(t, err, "failed to load document") + mustf(t, err, "failed to load document") collector := revisor.NewValueCollector() @@ -83,7 +83,7 @@ func testCollectionAgainstGolden( _, err = testCase.Validator.ValidateDocument(ctx, &document, revisor.WithValueCollector(collector)) - must(t, err, "validate document") + mustf(t, err, "validate document") collected := make(map[string]collectedValues) @@ -118,18 +118,18 @@ func testCollectionAgainstGolden( if regenerate { data, err := json.MarshalIndent(collected, "", " ") - must(t, err, "marshal for golden reference file") + mustf(t, err, "marshal for golden reference file") data = append(data, '\n') err = os.WriteFile(goldenPath, data, 0o600) - must(t, err, "write golden reference file") + mustf(t, err, "write golden reference file") } var want map[string]collectedValues err = internal.UnmarshalFile(goldenPath, &want) - must(t, err, "failed to load expected result") + mustf(t, err, "failed to load expected result") if diff := cmp.Diff(want, collected); diff != "" { t.Fatalf("collection mismatch (-want +got):\n%s", @@ -139,7 +139,7 @@ func testCollectionAgainstGolden( } //nolint:unparam -func must(t *testing.T, err error, format string, a ...any) { +func mustf(t *testing.T, err error, format string, a ...any) { t.Helper() if err != nil { diff --git a/deprecation_test.go b/deprecation_test.go index 2838aa8..83bbb0d 100644 --- a/deprecation_test.go +++ b/deprecation_test.go @@ -25,12 +25,12 @@ func TestDeprecation(t *testing.T) { ) testValidator, err := revisor.NewValidator(testConstraints...) - must(t, err, "failed to create test validator") + mustf(t, err, "failed to create test validator") var document newsdoc.Document err = internal.UnmarshalFile("testdata/geo.json", &document) - must(t, err, "unmarshal geo doc") + mustf(t, err, "unmarshal geo doc") var ( got []deprecationEntry @@ -58,7 +58,7 @@ func TestDeprecation(t *testing.T) { res, err := testValidator.ValidateDocument(ctx, &document, revisor.WithDeprecationHandler(deprecationHandler)) - must(t, err, "validate document") + mustf(t, err, "validate document") t.Run("FoundDeprecations", func(t *testing.T) { var ( @@ -68,14 +68,14 @@ func TestDeprecation(t *testing.T) { if regenerate { data, err := json.MarshalIndent(got, "", " ") - must(t, err, "marshal for golden reference file") + mustf(t, err, "marshal for golden reference file") err = os.WriteFile(goldenPath, data, 0o600) - must(t, err, "write golden reference file") + mustf(t, err, "write golden reference file") } err = internal.UnmarshalFile(goldenPath, &want) - must(t, err, "unmarshal golden file") + mustf(t, err, "unmarshal golden file") if diff := cmp.Diff(want, got); diff != "" { t.Fatalf("deprecation mismatch (-want +got):\n%s", @@ -100,14 +100,14 @@ func TestDeprecation(t *testing.T) { if regenerate { data, err := json.MarshalIndent(got, "", " ") - must(t, err, "marshal for golden reference file") + mustf(t, err, "marshal for golden reference file") err = os.WriteFile(goldenPath, data, 0o600) - must(t, err, "write golden reference file") + mustf(t, err, "write golden reference file") } err = internal.UnmarshalFile(goldenPath, &want) - must(t, err, "unmarshal golden file") + mustf(t, err, "unmarshal golden file") if diff := cmp.Diff(want, got); diff != "" { t.Fatalf("enforcement mismatch (-want +got):\n%s", diff --git a/document_constraint.go b/document_constraint.go index 5a802d8..b48c96c 100644 --- a/document_constraint.go +++ b/document_constraint.go @@ -1,6 +1,9 @@ package revisor import ( + "slices" + "strings" + "github.com/ttab/newsdoc" ) @@ -52,7 +55,7 @@ func (dc DocumentConstraint) Matches( d *newsdoc.Document, vCtx *ValidationContext, ) Match { if dc.Declares != "" { - if d.Type == dc.Declares { + if d.Type == dc.Declares || resolveVariant(d.Type, vCtx.variants) == dc.Declares { return MatchDeclaration } @@ -60,7 +63,7 @@ func (dc DocumentConstraint) Matches( } for _, k := range dc.Match.Keys { - value, ok := documentMatchAttribute(d, k) + value, ok := documentMatchAttribute(d, k, vCtx.variants) if !ok { return NoMatch } @@ -95,9 +98,9 @@ const ( docAttrURL documentAttributeKey = "url" ) -func documentMatchAttribute(d *newsdoc.Document, name string) (string, bool) { +func documentMatchAttribute(d *newsdoc.Document, name string, variants []Variant) (string, bool) { if documentAttributeKey(name) == docAttrType { - return d.Type, true + return resolveVariant(d.Type, variants), true } return "", false @@ -121,3 +124,41 @@ func documentAttribute(d *newsdoc.Document, name string) (string, bool) { return "", false } + +// Variant defines a document type variant suffix. When a document type +// contains a "+" separator (e.g. "core/article+template"), the part after the +// last "+" is matched against configured variants. If Types is empty, the +// variant applies to all declared document types. +type Variant struct { + Name string `json:"name"` + Types []string `json:"types,omitempty"` +} + +// resolveVariant returns the base document type if the suffix after the last +// "+" matches a configured variant (and the base type is allowed for that +// variant). Returns docType unchanged if no variant matches. +func resolveVariant(docType string, variants []Variant) string { + idx := strings.LastIndex(docType, "+") + if idx == -1 { + return docType + } + + base := docType[:idx] + suffix := docType[idx+1:] + + for i := range variants { + if variants[i].Name != suffix { + continue + } + + if len(variants[i].Types) == 0 { + return base + } + + if slices.Contains(variants[i].Types, base) { + return base + } + } + + return docType +} diff --git a/string_constraint.go b/string_constraint.go index 943dc1e..a5dd2a4 100644 --- a/string_constraint.go +++ b/string_constraint.go @@ -182,8 +182,9 @@ func (sc StringConstraint) Requirement() string { } type ValidationContext struct { - coll ValueCollector - depr DeprecationHandlerFunc + coll ValueCollector + depr DeprecationHandlerFunc + variants []Variant ValidateHTML func(policyName, value string) error ValidateEnum func(enum string, value string) (*Deprecation, error) diff --git a/testdata/example-article-template.json b/testdata/example-article-template.json new file mode 100644 index 0000000..2489e5a --- /dev/null +++ b/testdata/example-article-template.json @@ -0,0 +1,133 @@ +{ + "uuid": "98cedf81-ce8d-4dc4-bcbc-6091a756cc5c", + "type": "core/article+template", + "uri": "core://article/98cedf81-ce8d-4dc4-bcbc-6091a756cc5c", + "title": "champ-inter", + "content": [ + { + "id": "NzUsMjQ2LDIwOCwxNTE", + "type": "core/text", + "role": "heading-1", + "data": { + "text": "Pånyttfödd stjärna vill skjuta Inter till final" + } + }, + { + "id": "MTQ1LDE5OSwyMzMsMjE3", + "type": "tt/visual", + "data": { + "caption": "Inters Romelu Lukaku firar ett av sina två mål i helgens ligamatch (4–2) mot Sassuolo." + }, + "links": [ + { + "uri": "http://tt.se/media/image/sdlAh_UuIyXsC0", + "url": "https://tt.se/media/image/sdlAh_UuIyXsC0_NormalPreview.jpg", + "type": "tt/picture", + "data": { + "credit": "Spada/AP/TT", + "height": "683", + "hiresScale": "2.9052734375", + "width": "1024" + }, + "rel": "self" + } + ] + }, + { + "id": "pagedateline-5b8149a71b05cef3c73af71ca95c555d", + "type": "core/text", + "role": "vignette", + "data": { + "text": "Fotboll" + } + }, + { + "id": "preamble-a516335827925ef7573589968e87e6ed", + "type": "core/text", + "role": "preamble", + "data": { + "text": "Skador och ett VM-fiasko såg länge ut att prägla Romelu Lukakus säsong." + } + }, + { + "id": "preamble-606c08bdfc665b45ae1a771ec3c94164", + "type": "core/text", + "role": "preamble", + "data": { + "text": "Men den belgiske anfallsstjärnan har återuppstått." + } + }, + { + "id": "preamble-a3775318ca3d05815028f9fcdc432c41", + "type": "core/text", + "role": "preamble", + "data": { + "text": "Nu vill han skjuta sitt Inter till Champions League-final." + } + }, + { + "id": "MjEwLDQyLDI3LDgy", + "type": "core/text", + "data": { + "text": "Det var nog inte många som trodde på en framgångsrik Champions League-säsong för Inter, eller ens avancemang till slutspel, efter den första matchen i september. 0–2 hemma mot Bayern München var precis den start man inte önskade i grupp C, som också innehöll ett stjärnspäckat Barcelona." + } + }, + { + "id": "NTcsMTkwLDE1OCwxMTA", + "type": "core/text", + "role": "heading-2", + "data": { + "text": "Lukaku i målform" + } + }, + { + "id": "paragraph-05add01557d5b0c4d5cb814676aea424", + "type": "core/text", + "data": { + "text": "En 1–0-seger hemma mot Barcelona och 3–3 på bortaplan mot samma lag lade sedan grunden till avancemanget. I slutspelet har dubbla portugisiska hinder passerats – Porto i åttondel, Benfica i kvartsfinal – innan man nu skaffat sig ett ypperligt utgångsläge inför tisdagens semifinalretur mot Milan." + } + } + ], + "meta": [ + { + "type": "core/newsvalue", + "value": "3", + "data": { + "duration": "86400" + } + }, + { + "type": "tt/slugline", + "value": "champ-inter" + } + ], + "links": [ + { + "uuid": "9187712c-a795-4cdc-bd4e-560e4f93dc4e", + "type": "core/author", + "title": "Lasse Mannheimer/TT", + "data": { + "email": "the-email-address@tt.se", + "shortDescription": "lma" + }, + "rel": "author" + }, + { + "uuid": "e42bcd66-9823-4895-9966-af50056ea933", + "type": "core/assignment", + "rel": "assignment" + }, + { + "uuid": "0730efa9-43f2-468d-979a-aaffc74d7582", + "type": "core/section", + "title": "Sport", + "rel": "section" + }, + { + "uri": "tt://content-source/tt", + "type": "core/content-source", + "rel": "source" + } + ], + "language": "sv" +} diff --git a/testdata/results/example-example-article-template.json b/testdata/results/example-example-article-template.json new file mode 100644 index 0000000..19765bd --- /dev/null +++ b/testdata/results/example-example-article-template.json @@ -0,0 +1 @@ +null diff --git a/validation.go b/validation.go index 37abf8e..483b42b 100644 --- a/validation.go +++ b/validation.go @@ -15,6 +15,7 @@ import ( type Validator struct { constraints []ConstraintSet + variants []Variant blocks map[BlockKind]map[string]*BlockConstraint documents []*DocumentConstraint @@ -193,6 +194,15 @@ func collectBlockDeclarations( return nil } +// WithVariants returns a shallow copy of the Validator configured with the +// given document type variants. +func (v *Validator) WithVariants(variants ...Variant) *Validator { + nv := *v + nv.variants = variants + + return &nv +} + // WithConstraints returns a new Validator that uses an additional set of // constraints. func (v *Validator) WithConstraints( @@ -202,7 +212,14 @@ func (v *Validator) WithConstraints( c = append(c, constraints...) - return NewValidator(c...) + nv, err := NewValidator(c...) + if err != nil { + return nil, err + } + + nv.variants = v.variants + + return nv, nil } type ValidationResult struct { @@ -370,6 +387,7 @@ func (v *Validator) ValidateDocument( vCtx := ValidationContext{ coll: ValueDiscarder{}, + variants: v.variants, ValidateHTML: v.validateHTML, ValidateEnum: v.enums.ValidValue, } diff --git a/validation_test.go b/validation_test.go index a821b34..800517f 100644 --- a/validation_test.go +++ b/validation_test.go @@ -95,9 +95,9 @@ func FuzzValidationWide(f *testing.F) { extraConstraints revisor.ConstraintSet ) - if !(decodeBytes(t, constraintsA, &constraints) && - decodeBytes(t, constraintsB, &extraConstraints) && - decodeBytes(t, documentData, &document)) { + if !decodeBytes(t, constraintsA, &constraints) || + !decodeBytes(t, constraintsB, &extraConstraints) || + !decodeBytes(t, documentData, &document) { return } @@ -268,6 +268,10 @@ func TestValidateDocument(t *testing.T) { t.Fatalf("failed to create extended validator: %v", err) } + orgValidator = orgValidator.WithVariants(revisor.Variant{ + Name: "template", + }) + testConstraints := decodeConstraintSets(t, "testdata/constraints/geo.json", "testdata/constraints/labels-hints.json", @@ -339,25 +343,25 @@ func testAgainstGolden( ) err := internal.UnmarshalFile(sourceDocPath, &document) - must(t, err, "failed to load document") + mustf(t, err, "failed to load document") ctx := context.Background() got, err := testCase.Validator.ValidateDocument(ctx, &document) - must(t, err, "validate document") + mustf(t, err, "validate document") if regenerate { goldie, err := json.MarshalIndent(got, "", " ") - must(t, err, "marshal new golden results") + mustf(t, err, "marshal new golden results") goldie = append(goldie, '\n') err = os.WriteFile(goldenPath, goldie, 0o600) - must(t, err, "write updated golden file") + mustf(t, err, "write updated golden file") } err = internal.UnmarshalFile(goldenPath, &want) - must(t, err, "failed to load expected result") + mustf(t, err, "failed to load expected result") for i := range got { if !resultHas(want, got[i]) { @@ -405,3 +409,124 @@ func equalResult(a, b revisor.ValidationResult) bool { return true } + +func TestTemplateDocumentType(t *testing.T) { + constraintSet := revisor.ConstraintSet{ + Name: "test-template", + Version: 1, + Documents: []revisor.DocumentConstraint{ + { + Declares: "core/article", + Name: "Article", + }, + }, + } + + baseValidator, err := revisor.NewValidator(constraintSet) + if err != nil { + t.Fatalf("failed to create validator: %v", err) + } + + validator := baseValidator.WithVariants(revisor.Variant{Name: "template"}) + + ctx := context.Background() + + t.Run("TemplateMatchesDeclares", func(t *testing.T) { + doc := newsdoc.Document{ + Type: "core/article+template", + UUID: "00000000-0000-0000-0000-000000000001", + URI: "article://test/1", + } + + results, err := validator.ValidateDocument(ctx, &doc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + for _, r := range results { + if strings.Contains(r.Error, "undeclared document type") { + t.Errorf("template document should not be undeclared, got: %s", r.Error) + } + } + }) + + t.Run("UnsupportedSuffixRejected", func(t *testing.T) { + doc := newsdoc.Document{ + Type: "core/article+other", + UUID: "00000000-0000-0000-0000-000000000001", + URI: "article://test/1", + } + + results, err := validator.ValidateDocument(ctx, &doc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + found := false + + for _, r := range results { + if strings.Contains(r.Error, "undeclared document type") { + found = true + + break + } + } + + if !found { + t.Error("expected undeclared document type error for +other suffix") + } + }) + + t.Run("BaseTypeStillMatches", func(t *testing.T) { + doc := newsdoc.Document{ + Type: "core/article", + UUID: "00000000-0000-0000-0000-000000000001", + URI: "article://test/1", + } + + results, err := validator.ValidateDocument(ctx, &doc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + for _, r := range results { + if strings.Contains(r.Error, "undeclared document type") { + t.Errorf("base document type should still match, got: %s", r.Error) + } + } + }) + + t.Run("TypeRestrictedVariant", func(t *testing.T) { + restrictedValidator := baseValidator.WithVariants( + revisor.Variant{ + Name: "template", + Types: []string{"core/planning"}, + }, + ) + + doc := newsdoc.Document{ + Type: "core/article+template", + UUID: "00000000-0000-0000-0000-000000000001", + URI: "article://test/1", + } + + results, err := restrictedValidator.ValidateDocument(ctx, &doc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + found := false + + for _, r := range results { + if strings.Contains(r.Error, "undeclared document type") { + found = true + + break + } + } + + if !found { + t.Error("expected undeclared document type error for type-restricted variant") + } + }) +} From 08c9eec56d1cba83bf5ddc1bedbcfec72be59c8d Mon Sep 17 00:00:00 2001 From: Hugo Wetterberg Date: Thu, 12 Feb 2026 15:24:34 +0100 Subject: [PATCH 2/3] document variants usage --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 3f8ce00..3f4c553 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,39 @@ A list of available block attributes, and whether they can be used in pattern ma | contenttype | The content type of the resource that the block describes | Yes | | role | The role that the block or resource has | Yes | +## Document type variants + +Document type variants allow documents to use a suffixed type like `"core/article+template"` and still match the base declaration `"core/article"`. Variants are configured on the validator, not in constraint sets, so that the set of allowed suffixes is controlled by the application. + +Configure variants using `WithVariants`: + +``` go +validator, err := revisor.NewValidator(constraints...) +if err != nil { + log.Fatal(err) +} + +// Allow "+template" for all declared document types. +validator = validator.WithVariants(revisor.Variant{ + Name: "template", +}) + +// Or restrict a variant to specific base types. +validator = validator.WithVariants( + revisor.Variant{ + Name: "template", + }, + revisor.Variant{ + Name: "example", + Types: []string{"core/planning-item"}, + }, +) +``` + +When `Types` is empty the variant applies to all declared document types. When `Types` is set the variant only applies to the listed base types; a document with a non-matching base type will be treated as an undeclared type. + +Variants are preserved across `WithConstraints` calls. + ## Testing Revisor implements a file-driven test in `TestValidateDocument` that checks so that all the "testdata/results/*.json" files match the validation results for the corresponding document under "testdata/". Result files with the prefix "base-" will be validated against "constraints/naviga.json", for result files with the prefix "example-" the "constraints/example.json" constraints will be used as well. From 8364ecc73f56ef79cb7e662a1b4dd135baf75a35 Mon Sep 17 00:00:00 2001 From: Hugo Wetterberg Date: Thu, 12 Feb 2026 22:27:58 +0100 Subject: [PATCH 3/3] add a test case that verifies that value extraction works with variants --- collection_test.go | 4 ++ testdata/geo-info-template.json | 28 ++++++++ .../test-geo-info-template.json | 66 +++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 testdata/geo-info-template.json create mode 100644 testdata/results-collection/test-geo-info-template.json diff --git a/collection_test.go b/collection_test.go index e31455e..9479859 100644 --- a/collection_test.go +++ b/collection_test.go @@ -28,6 +28,10 @@ func TestCollection(t *testing.T) { testValidator, err := revisor.NewValidator(testConstraints...) mustf(t, err, "failed to create test validator") + testValidator = testValidator.WithVariants(revisor.Variant{ + Name: "template", + }) + tests := []validatorTest{ { Name: "TestConf", diff --git a/testdata/geo-info-template.json b/testdata/geo-info-template.json new file mode 100644 index 0000000..e685105 --- /dev/null +++ b/testdata/geo-info-template.json @@ -0,0 +1,28 @@ +{ + "type": "core/place+template", + "uuid": "d9a8a437-3ed3-4b97-b9fc-cf236b6dc5e7", + "title": "Some place", + "meta": [ + { + "type": "core/place", + "role": "absurd", + "data": { + "position": "POINT(12 15)" + } + }, + { + "type": "extra/entity-info", + "data": { + "shortcode": "someplace", + "description": "A place where things are" + } + }, + { + "type": "core/place", + "uri": "place://old", + "data": { + "position": "LINESTRING(30.123 10.15)" + } + } + ] +} diff --git a/testdata/results-collection/test-geo-info-template.json b/testdata/results-collection/test-geo-info-template.json new file mode 100644 index 0000000..d4496aa --- /dev/null +++ b/testdata/results-collection/test-geo-info-template.json @@ -0,0 +1,66 @@ +{ + "meta.core_place.data.position": { + "format": "wkt", + "values": [ + "POINT(12 15)", + "LINESTRING(30.123 10.15)" + ], + "labels": [ + "document-position" + ] + }, + "meta.core_place.role": { + "values": [ + "absurd" + ] + }, + "meta.core_place.type": { + "values": [ + "core/place" + ] + }, + "meta.core_place.uri": { + "values": [ + "place://old" + ] + }, + "meta.extra_entity_info.data.description": { + "values": [ + "A place where things are" + ], + "hints": { + "alias": [ + "description" + ] + } + }, + "meta.extra_entity_info.data.shortcode": { + "values": [ + "someplace" + ], + "hints": { + "alias": [ + "shortcode" + ] + }, + "labels": [ + "keyword", + "identifier" + ] + }, + "meta.extra_entity_info.type": { + "values": [ + "extra/entity-info" + ] + }, + "title": { + "values": [ + "Some place" + ] + }, + "type": { + "values": [ + "core/place" + ] + } +}