|  | 
| 9 | 9 | 
 | 
| 10 | 10 | package org.elasticsearch.index.mapper; | 
| 11 | 11 | 
 | 
|  | 12 | +import org.elasticsearch.ExceptionsHelper; | 
| 12 | 13 | import org.elasticsearch.common.Strings; | 
| 13 | 14 | import org.elasticsearch.common.xcontent.XContentHelper; | 
| 14 | 15 | import org.elasticsearch.core.CheckedConsumer; | 
| @@ -370,6 +371,250 @@ public void testEmptyType() throws Exception { | 
| 370 | 371 |         assertThat(e.getMessage(), containsString("type cannot be an empty string")); | 
| 371 | 372 |     } | 
| 372 | 373 | 
 | 
|  | 374 | +    public void testWithRootObjectMapperNamespaceValidator() throws Exception { | 
|  | 375 | +        final String errorMessage = "error 1234"; | 
|  | 376 | + | 
|  | 377 | +        RootObjectMapperNamespaceValidator validatorx = new RootObjectMapperNamespaceValidator() { | 
|  | 378 | +            @Override | 
|  | 379 | +            public void validateNamespace(ObjectMapper.Subobjects subobjects, String name) { | 
|  | 380 | +                System.err.println(">>> XXX subobjects: " + subobjects.toString()); | 
|  | 381 | +                String disallowed = "_project"; | 
|  | 382 | +                if (name.equals(disallowed)) { | 
|  | 383 | +                    throw new IllegalArgumentException(errorMessage); | 
|  | 384 | +                } else if (subobjects != ObjectMapper.Subobjects.ENABLED) { | 
|  | 385 | +                    System.err.println(">>> YYYYYYYYYYYYYYYYY subobjects: " + subobjects.toString()); | 
|  | 386 | +                    // name here will be something like _project.my_field, rather than just _project | 
|  | 387 | +                    if (name.startsWith(disallowed + ".")) { | 
|  | 388 | +                        throw new IllegalArgumentException(errorMessage); | 
|  | 389 | +                    } | 
|  | 390 | +                } | 
|  | 391 | +            } | 
|  | 392 | +        }; | 
|  | 393 | + | 
|  | 394 | +        String notNested = """ | 
|  | 395 | +            { | 
|  | 396 | +                "_doc": { | 
|  | 397 | +                    "properties": { | 
|  | 398 | +                        "<FIELD_NAME>": { | 
|  | 399 | +                            "type": "<TYPE>" | 
|  | 400 | +                        } | 
|  | 401 | +                    } | 
|  | 402 | +                } | 
|  | 403 | +            }"""; | 
|  | 404 | + | 
|  | 405 | +        // _project should fail, regardless of type | 
|  | 406 | +        { | 
|  | 407 | +            String json = notNested.replace("<FIELD_NAME>", "_project"); | 
|  | 408 | + | 
|  | 409 | +            String keyword = json.replace("<TYPE>", "keyword"); | 
|  | 410 | +            Exception e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(keyword, validator)); | 
|  | 411 | +            assertThat(e.getMessage(), equalTo(errorMessage)); | 
|  | 412 | + | 
|  | 413 | +            String text = json.replace("<TYPE>", "text"); | 
|  | 414 | +            e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(text, validator)); | 
|  | 415 | +            assertThat(e.getMessage(), equalTo(errorMessage)); | 
|  | 416 | + | 
|  | 417 | +            String object = json.replace("<TYPE>", "object"); | 
|  | 418 | +            e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(object, validator)); | 
|  | 419 | +            assertThat(e.getMessage(), equalTo(errorMessage)); | 
|  | 420 | +        } | 
|  | 421 | + | 
|  | 422 | +        // _project.subfield should fail | 
|  | 423 | +        { | 
|  | 424 | +            String json = notNested.replace("<FIELD_NAME>", "_project.subfield").replace("<TYPE>", randomFrom("text", "keyword", "object")); | 
|  | 425 | +            Exception e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(json, validator)); | 
|  | 426 | +            assertThat(e.getMessage(), equalTo(errorMessage)); | 
|  | 427 | +        } | 
|  | 428 | + | 
|  | 429 | +        // _projectx should pass | 
|  | 430 | +        { | 
|  | 431 | +            String json = notNested.replace("<FIELD_NAME>", "_projectx").replace("<TYPE>", randomFrom("text", "keyword", "object")); | 
|  | 432 | +            MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 433 | +            assertNotNull(mapperService); | 
|  | 434 | +        } | 
|  | 435 | + | 
|  | 436 | +        // _project_subfield should pass | 
|  | 437 | +        { | 
|  | 438 | +            String json = notNested.replace("<FIELD_NAME>", "_project_subfield"); | 
|  | 439 | +            json = json.replace("<TYPE>", randomFrom("text", "keyword", "object")); | 
|  | 440 | +            MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 441 | +            assertNotNull(mapperService); | 
|  | 442 | +        } | 
|  | 443 | + | 
|  | 444 | +        // _projectx.subfield should pass | 
|  | 445 | +        { | 
|  | 446 | +            String json = notNested.replace("<FIELD_NAME>", "_projectx.subfield"); | 
|  | 447 | +            json = json.replace("<TYPE>", randomFrom("text", "keyword", "object")); | 
|  | 448 | +            MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 449 | +            assertNotNull(mapperService); | 
|  | 450 | +        } | 
|  | 451 | + | 
|  | 452 | +        String nested = """ | 
|  | 453 | +            { | 
|  | 454 | +                "_doc": { | 
|  | 455 | +                    "properties": { | 
|  | 456 | +                        "<FIELD_NAME1>": { | 
|  | 457 | +                            "type": "object", | 
|  | 458 | +                            "properties": { | 
|  | 459 | +                                "<FIELD_NAME1>": { | 
|  | 460 | +                                    "type": "keyword" | 
|  | 461 | +                                } | 
|  | 462 | +                            } | 
|  | 463 | +                        } | 
|  | 464 | +                    } | 
|  | 465 | +                } | 
|  | 466 | +            }"""; | 
|  | 467 | + | 
|  | 468 | +        // TODO: this also works - what is the difference? | 
|  | 469 | +        // String nested = """ | 
|  | 470 | +        // { | 
|  | 471 | +        // "mappings": { | 
|  | 472 | +        // "properties": { | 
|  | 473 | +        // "<FIELD_NAME1>": { | 
|  | 474 | +        // "type": "object", | 
|  | 475 | +        // "properties": { | 
|  | 476 | +        // "<FIELD_NAME1>": { | 
|  | 477 | +        // "type": "keyword" | 
|  | 478 | +        // } | 
|  | 479 | +        // } | 
|  | 480 | +        // } | 
|  | 481 | +        // } | 
|  | 482 | +        // } | 
|  | 483 | +        // }"""; | 
|  | 484 | + | 
|  | 485 | +        // nested _project { my_field } should fail | 
|  | 486 | +        { | 
|  | 487 | +            String json = nested.replace("<FIELD_NAME1>", "_project").replace("<FIELD_NAME2>", "my_field"); | 
|  | 488 | +            Exception e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(json, validator)); | 
|  | 489 | +            assertThat(e.getMessage(), equalTo(errorMessage)); | 
|  | 490 | +        } | 
|  | 491 | + | 
|  | 492 | +        // nested my_field { _project } should succeed | 
|  | 493 | +        { | 
|  | 494 | +            String json = nested.replace("<FIELD_NAME1>", "my_field").replace("<FIELD_NAME2>", "_project"); | 
|  | 495 | +            MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 496 | +            assertNotNull(mapperService); | 
|  | 497 | +        } | 
|  | 498 | + | 
|  | 499 | +        // nested _projectx { _project } should succeed | 
|  | 500 | +        { | 
|  | 501 | +            String json = nested.replace("<FIELD_NAME1>", "_projectx").replace("<FIELD_NAME2>", "_project"); | 
|  | 502 | +            MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 503 | +            assertNotNull(mapperService); | 
|  | 504 | +        } | 
|  | 505 | + | 
|  | 506 | +        // TODO: I'm not allowed to change the subobjects setting for some reason | 
|  | 507 | +        // test with subobjects setting | 
|  | 508 | +        String withSubobjects = """ | 
|  | 509 | +            { | 
|  | 510 | +                "_doc": { | 
|  | 511 | +                    "subobjects": "<SUBOBJECTS_SETTING>", | 
|  | 512 | +                    "properties": { | 
|  | 513 | +                        "<FIELD_NAME>": { | 
|  | 514 | +                            "type": "object", | 
|  | 515 | +                            "properties": { | 
|  | 516 | +                                "my_field": { | 
|  | 517 | +                                    "type": "keyword" | 
|  | 518 | +                                } | 
|  | 519 | +                            } | 
|  | 520 | +                        } | 
|  | 521 | +                    } | 
|  | 522 | +                } | 
|  | 523 | +            }"""; | 
|  | 524 | + | 
|  | 525 | +        // String withSubobjects = """ | 
|  | 526 | +        // { | 
|  | 527 | +        // "mappings": { | 
|  | 528 | +        // "subobjects": "<SUBOBJECTS_SETTING>", | 
|  | 529 | +        // "properties": { | 
|  | 530 | +        // "<FIELD_NAME>": { | 
|  | 531 | +        // "type": "object", | 
|  | 532 | +        // "properties": { | 
|  | 533 | +        // "my_field": { | 
|  | 534 | +        // "type": "keyword" | 
|  | 535 | +        // } | 
|  | 536 | +        // } | 
|  | 537 | +        // } | 
|  | 538 | +        // } | 
|  | 539 | +        // } | 
|  | 540 | +        // }"""; | 
|  | 541 | +        // | 
|  | 542 | +        // { | 
|  | 543 | +        // String json = withSubobjects.replace("<SUBOBJECTS_SETTING>", "false")//randomFrom("true", "false", "auto")) | 
|  | 544 | +        // .replace("<FIELD_NAME>", "abc"); | 
|  | 545 | +        // MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 546 | +        // assertNotNull(mapperService); | 
|  | 547 | +        // } | 
|  | 548 | + | 
|  | 549 | +        // { | 
|  | 550 | +        // String json = withSubobjects.replace("<SUBOBJECTS_SETTING>", "false") | 
|  | 551 | +        // .replace("<FIELD_NAME>", "_project"); | 
|  | 552 | +        // // TODO: fails with org.elasticsearch.index.mapper.MapperException: the [subobjects] parameter can't be updated for the object | 
|  | 553 | +        // mapping [_doc] | 
|  | 554 | +        // MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator); | 
|  | 555 | +        // assertNotNull(mapperService); | 
|  | 556 | +        // } | 
|  | 557 | +    } | 
|  | 558 | + | 
|  | 559 | +    public void testRuntimeFieldInMappingWithNamespaceValidator() throws IOException { | 
|  | 560 | +        final String errorMessage = "error 1234"; | 
|  | 561 | + | 
|  | 562 | +        RootObjectMapperNamespaceValidator validator = new RootObjectMapperNamespaceValidator() { | 
|  | 563 | +            @Override | 
|  | 564 | +            public void validateNamespace(ObjectMapper.Subobjects subobjects, String name) { | 
|  | 565 | +                System.err.println(">>> XXX subobjects: " + subobjects); | 
|  | 566 | +                String disallowed = "_project"; | 
|  | 567 | +                if (name.equals(disallowed)) { | 
|  | 568 | +                    throw new IllegalArgumentException(errorMessage); | 
|  | 569 | +                } else if (subobjects != ObjectMapper.Subobjects.ENABLED) { | 
|  | 570 | +                    System.err.println(">>> YYYYYYYYYYYYYYYYY subobjects: " + subobjects); | 
|  | 571 | +                    // name here will be something like _project.my_field, rather than just _project | 
|  | 572 | +                    if (name.startsWith(disallowed + ".")) { | 
|  | 573 | +                        throw new IllegalArgumentException(errorMessage); | 
|  | 574 | +                    } | 
|  | 575 | +                } | 
|  | 576 | +            } | 
|  | 577 | +        }; | 
|  | 578 | + | 
|  | 579 | +        // ensure that things close to the disallowed fields that are allowed | 
|  | 580 | +        { | 
|  | 581 | +            String mapping = Strings.toString(runtimeMapping(builder -> { | 
|  | 582 | +                builder.startObject("_project_x").field("type", "ip").endObject(); | 
|  | 583 | +                builder.startObject("_projectx").field("type", "date").endObject(); | 
|  | 584 | +                builder.startObject("field1._project").field("type", "double").endObject(); | 
|  | 585 | +            })); | 
|  | 586 | +            MapperService mapperService = createMapperServiceWithNamespaceValidator(mapping, validator); | 
|  | 587 | +            assertEquals(mapping, mapperService.documentMapper().mappingSource().toString()); | 
|  | 588 | +            assertEquals(3, mapperService.documentMapper().mapping().getRoot().getTotalFieldsCount()); | 
|  | 589 | +        } | 
|  | 590 | + | 
|  | 591 | +        // _project is rejected | 
|  | 592 | +        { | 
|  | 593 | +            String mapping = Strings.toString(runtimeMapping(builder -> { | 
|  | 594 | +                builder.startObject("field1").field("type", "double").endObject(); | 
|  | 595 | +                builder.startObject("_project").field("type", "date").endObject(); | 
|  | 596 | +                builder.startObject("field3").field("type", "ip").endObject(); | 
|  | 597 | +            })); | 
|  | 598 | +            Exception e = expectThrows(MapperParsingException.class, () -> createMapperServiceWithNamespaceValidator(mapping, validator)); | 
|  | 599 | +            Throwable cause = ExceptionsHelper.unwrap(e, IllegalArgumentException.class); | 
|  | 600 | +            assertNotNull(cause); | 
|  | 601 | +            assertThat(cause.getMessage(), equalTo(errorMessage)); | 
|  | 602 | +        } | 
|  | 603 | + | 
|  | 604 | +        // _project.my_sub_field is rejected | 
|  | 605 | +        { | 
|  | 606 | +            String mapping = Strings.toString(runtimeMapping(builder -> { | 
|  | 607 | +                builder.startObject("field1").field("type", "double").endObject(); | 
|  | 608 | +                builder.startObject("_project.my_sub_field").field("type", "keyword").endObject(); | 
|  | 609 | +                builder.startObject("field3").field("type", "ip").endObject(); | 
|  | 610 | +            })); | 
|  | 611 | +            Exception e = expectThrows(MapperParsingException.class, () -> createMapperServiceWithNamespaceValidator(mapping, validator)); | 
|  | 612 | +            Throwable cause = ExceptionsHelper.unwrap(e, IllegalArgumentException.class); | 
|  | 613 | +            assertNotNull(cause); | 
|  | 614 | +            assertThat(cause.getMessage(), equalTo(errorMessage)); | 
|  | 615 | +        } | 
|  | 616 | +    } | 
|  | 617 | + | 
| 373 | 618 |     public void testSyntheticSourceKeepAllThrows() throws IOException { | 
| 374 | 619 |         String mapping = Strings.toString( | 
| 375 | 620 |             XContentFactory.jsonBuilder() | 
|  | 
0 commit comments