diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AbstractSchemaProvider.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AbstractSchemaProvider.java deleted file mode 100644 index d7e023b76d5..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AbstractSchemaProvider.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.schemas.registry; - -import static com.google.common.base.Preconditions.checkArgument; - -import java.util.Map; -import java.util.NavigableMap; -import java.util.TreeMap; -import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.schemas.registry.SchemaTypes.SchemaId; - -abstract class AbstractSchemaProvider implements SchemaProvider { - private final NavigableMap milestoneToEffectiveMilestone = - new TreeMap<>(); - private final SchemaId schemaId; - - protected AbstractSchemaProvider(final SchemaId schemaId) { - this.schemaId = schemaId; - } - - protected void addMilestoneMapping( - final SpecMilestone milestone, final SpecMilestone untilMilestone) { - checkArgument( - untilMilestone.isGreaterThanOrEqualTo(milestone), - "%s must be earlier then or equal to %s", - milestone, - untilMilestone); - - checkOverlappingVersionMappings(milestone, untilMilestone); - - SpecMilestone currentMilestone = untilMilestone; - while (currentMilestone.isGreaterThan(milestone)) { - milestoneToEffectiveMilestone.put(currentMilestone, milestone); - currentMilestone = currentMilestone.getPreviousMilestone(); - } - } - - private void checkOverlappingVersionMappings( - final SpecMilestone milestone, final SpecMilestone untilMilestone) { - final Map.Entry floorEntry = - milestoneToEffectiveMilestone.floorEntry(untilMilestone); - if (floorEntry != null && floorEntry.getValue().isGreaterThanOrEqualTo(milestone)) { - throw new IllegalArgumentException( - String.format( - "Milestone %s is already mapped to %s", - floorEntry.getKey(), getEffectiveMilestone(floorEntry.getValue()))); - } - final Map.Entry ceilingEntry = - milestoneToEffectiveMilestone.ceilingEntry(milestone); - if (ceilingEntry != null && ceilingEntry.getKey().isLessThanOrEqualTo(untilMilestone)) { - throw new IllegalArgumentException( - String.format( - "Milestone %s is already mapped to %s", - ceilingEntry.getKey(), getEffectiveMilestone(ceilingEntry.getValue()))); - } - } - - @Override - public SpecMilestone getEffectiveMilestone(final SpecMilestone milestone) { - return milestoneToEffectiveMilestone.getOrDefault(milestone, milestone); - } - - @Override - public T getSchema(final SchemaRegistry registry) { - final SpecMilestone milestone = registry.getMilestone(); - final SpecMilestone effectiveMilestone = getEffectiveMilestone(milestone); - return createSchema(registry, effectiveMilestone, registry.getSpecConfig()); - } - - @Override - public SchemaId getSchemaId() { - return schemaId; - } - - protected abstract T createSchema( - SchemaRegistry registry, SpecMilestone effectiveMilestone, SpecConfig specConfig); -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AttestationSchemaProvider.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AttestationSchemaProvider.java deleted file mode 100644 index 120d9d5ecbd..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AttestationSchemaProvider.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.schemas.registry; - -import static tech.pegasys.teku.spec.SpecMilestone.DENEB; -import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; -import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; -import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTESTATION_SCHEMA; - -import java.util.Set; -import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.datastructures.operations.Attestation; -import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; -import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; -import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttestationPhase0Schema; - -public class AttestationSchemaProvider - extends AbstractSchemaProvider> { - - public AttestationSchemaProvider() { - super(ATTESTATION_SCHEMA); - addMilestoneMapping(PHASE0, DENEB); - addMilestoneMapping(ELECTRA, SpecMilestone.getHighestMilestone()); - } - - @Override - protected AttestationSchema createSchema( - final SchemaRegistry registry, - final SpecMilestone effectiveMilestone, - final SpecConfig specConfig) { - return switch (effectiveMilestone) { - case PHASE0 -> - new AttestationPhase0Schema(specConfig.getMaxValidatorsPerCommittee()) - .castTypeToAttestationSchema(); - case ELECTRA -> - new AttestationElectraSchema( - (long) specConfig.getMaxValidatorsPerCommittee() - * specConfig.getMaxCommitteesPerSlot(), - specConfig.getMaxCommitteesPerSlot()) - .castTypeToAttestationSchema(); - default -> - throw new IllegalArgumentException( - "It is not supposed to create a specific version for " + effectiveMilestone); - }; - } - - @Override - public Set getSupportedMilestones() { - return ALL_MILESTONES; - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AttnetsENRFieldSchemaProvider.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AttnetsENRFieldSchemaProvider.java deleted file mode 100644 index bd01161eec4..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/AttnetsENRFieldSchemaProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.schemas.registry; - -import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; -import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTNETS_ENR_FIELD_SCHEMA; - -import java.util.Set; -import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; -import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.config.SpecConfig; - -public class AttnetsENRFieldSchemaProvider - extends AbstractSchemaProvider> { - public AttnetsENRFieldSchemaProvider() { - super(ATTNETS_ENR_FIELD_SCHEMA); - addMilestoneMapping(PHASE0, SpecMilestone.getHighestMilestone()); - } - - @Override - protected SszBitvectorSchema createSchema( - final SchemaRegistry registry, - final SpecMilestone effectiveMilestone, - final SpecConfig specConfig) { - return SszBitvectorSchema.create(specConfig.getAttestationSubnetCount()); - } - - @Override - public Set getSupportedMilestones() { - return ALL_MILESTONES; - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProvider.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProvider.java new file mode 100644 index 00000000000..95eeceda456 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProvider.java @@ -0,0 +1,153 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.schemas.registry; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.MoreObjects; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.BiFunction; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.schemas.registry.SchemaTypes.SchemaId; + +class BaseSchemaProvider implements SchemaProvider { + private final TreeMap> milestoneToSchemaCreator = + new TreeMap<>(); + private final SchemaId schemaId; + + private BaseSchemaProvider( + final SchemaId schemaId, + final List> schemaProviderCreators, + final SpecMilestone untilMilestone, + final boolean isConstant) { + this.schemaId = schemaId; + final List> creatorsList = new ArrayList<>(schemaProviderCreators); + + SchemaProviderCreator lastCreator = null; + + for (final SpecMilestone milestone : SpecMilestone.getMilestonesUpTo(untilMilestone)) { + if (!creatorsList.isEmpty() && creatorsList.getFirst().milestone == milestone) { + lastCreator = creatorsList.removeFirst(); + } + + if (lastCreator != null) { + milestoneToSchemaCreator.put( + milestone, isConstant ? lastCreator : lastCreator.withMilestone(milestone)); + } + } + } + + @Override + public SpecMilestone getEffectiveMilestone(final SpecMilestone milestone) { + return getSchemaCreator(milestone).milestone; + } + + @Override + public T getSchema(final SchemaRegistry registry) { + final SpecMilestone milestone = registry.getMilestone(); + return createSchema(registry, milestone, registry.getSpecConfig()); + } + + @Override + public SchemaId getSchemaId() { + return schemaId; + } + + protected T createSchema( + final SchemaRegistry registry, + final SpecMilestone effectiveMilestone, + final SpecConfig specConfig) { + return getSchemaCreator(effectiveMilestone).creator.apply(registry, specConfig); + } + + private SchemaProviderCreator getSchemaCreator(final SpecMilestone milestone) { + final SchemaProviderCreator maybeSchemaCreator = milestoneToSchemaCreator.get(milestone); + if (maybeSchemaCreator == null) { + throw new IllegalArgumentException( + "It is not supposed to create a specific version for " + milestone); + } + return maybeSchemaCreator; + } + + @Override + public Set getSupportedMilestones() { + return milestoneToSchemaCreator.keySet(); + } + + protected record SchemaProviderCreator( + SpecMilestone milestone, BiFunction creator) { + + private SchemaProviderCreator withMilestone(final SpecMilestone milestone) { + return new SchemaProviderCreator<>(milestone, creator); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("milestone", milestone).toString(); + } + } + + static Builder providerBuilder(final SchemaId schemaId) { + return new Builder<>(schemaId); + } + + static class Builder { + private final SchemaId schemaId; + final List> schemaProviderCreators = new ArrayList<>(); + private SpecMilestone untilMilestone = SpecMilestone.getHighestMilestone(); + private boolean isConstant = false; + + private Builder(final SchemaId schemaId) { + this.schemaId = schemaId; + } + + public Builder withCreator( + final SpecMilestone milestone, + final BiFunction creationSchema) { + checkArgument( + schemaProviderCreators.isEmpty() + || milestone.isGreaterThan(schemaProviderCreators.getLast().milestone), + "Creator's milestones must added in strict ascending order for %s", + schemaId); + + schemaProviderCreators.add(new SchemaProviderCreator<>(milestone, creationSchema)); + return this; + } + + public Builder until(final SpecMilestone untilMilestone) { + this.untilMilestone = untilMilestone; + return this; + } + + public Builder constant() { + this.isConstant = true; + return this; + } + + public BaseSchemaProvider build() { + checkArgument( + !schemaProviderCreators.isEmpty(), "There should be at least 1 creator for %s", schemaId); + + checkArgument( + untilMilestone.isGreaterThanOrEqualTo(schemaProviderCreators.getLast().milestone), + "until must be greater or equal than last creator milestone in %s", + schemaId); + return new BaseSchemaProvider<>(schemaId, schemaProviderCreators, untilMilestone, isConstant); + } + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaProvider.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaProvider.java index 7c6efcafd5a..75516b08fe5 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaProvider.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaProvider.java @@ -13,35 +13,11 @@ package tech.pegasys.teku.spec.schemas.registry; -import static tech.pegasys.teku.spec.SpecMilestone.BELLATRIX; -import static tech.pegasys.teku.spec.SpecMilestone.CAPELLA; -import static tech.pegasys.teku.spec.SpecMilestone.DENEB; -import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; - -import java.util.EnumSet; import java.util.Set; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.schemas.registry.SchemaTypes.SchemaId; interface SchemaProvider { - Set ALL_MILESTONES = EnumSet.allOf(SpecMilestone.class); - Set FROM_BELLATRIX = from(BELLATRIX); - Set FROM_CAPELLA = from(CAPELLA); - Set FROM_DENEB = from(DENEB); - Set FROM_ELECTRA = from(ELECTRA); - - static Set from(final SpecMilestone milestone) { - return EnumSet.copyOf(SpecMilestone.getAllMilestonesFrom(milestone)); - } - - static Set fromTo( - final SpecMilestone fromMilestone, final SpecMilestone toMilestone) { - return EnumSet.copyOf( - SpecMilestone.getAllMilestonesFrom(fromMilestone).stream() - .filter(toMilestone::isLessThanOrEqualTo) - .toList()); - } - T getSchema(SchemaRegistry registry); Set getSupportedMilestones(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java index 81788ca7d99..ddf5971ebf1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java @@ -13,11 +13,21 @@ package tech.pegasys.teku.spec.schemas.registry; +import static tech.pegasys.teku.spec.schemas.registry.BaseSchemaProvider.providerBuilder; +import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTESTATION_SCHEMA; +import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTNETS_ENR_FIELD_SCHEMA; + import com.google.common.annotations.VisibleForTesting; import java.util.HashSet; import java.util.Set; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; +import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttestationPhase0Schema; import tech.pegasys.teku.spec.schemas.registry.SchemaTypes.SchemaId; public class SchemaRegistryBuilder { @@ -27,8 +37,39 @@ public class SchemaRegistryBuilder { public static SchemaRegistryBuilder create() { return new SchemaRegistryBuilder() - .addProvider(new AttnetsENRFieldSchemaProvider()) - .addProvider(new AttestationSchemaProvider()); + // PHASE0 + .addProvider(createAttnetsENRFieldSchemaProvider()) + .addProvider(createAttestationSchemaProvider()); + } + + private static SchemaProvider> + createAttnetsENRFieldSchemaProvider() { + return providerBuilder(ATTNETS_ENR_FIELD_SCHEMA) + .constant() + .withCreator( + SpecMilestone.PHASE0, + (registry, specConfig) -> + SszBitvectorSchema.create(specConfig.getAttestationSubnetCount())) + .build(); + } + + private static SchemaProvider> createAttestationSchemaProvider() { + return providerBuilder(ATTESTATION_SCHEMA) + .constant() + .withCreator( + SpecMilestone.PHASE0, + (registry, specConfig) -> + new AttestationPhase0Schema(specConfig.getMaxValidatorsPerCommittee()) + .castTypeToAttestationSchema()) + .withCreator( + SpecMilestone.DENEB, + (registry, specConfig) -> + new AttestationElectraSchema( + (long) specConfig.getMaxValidatorsPerCommittee() + * specConfig.getMaxCommitteesPerSlot(), + specConfig.getMaxCommitteesPerSlot()) + .castTypeToAttestationSchema()) + .build(); } public SchemaRegistryBuilder() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java index 69e783dd28f..de164ca7f7a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java @@ -21,8 +21,13 @@ import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; public class SchemaTypes { // PHASE0 @@ -32,6 +37,12 @@ public class SchemaTypes { public static final SchemaId> ATTESTATION_SCHEMA = create("ATTESTATION_SCHEMA"); + public static final SchemaId> + EXECUTION_PAYLOAD_HEADER_SCHEMA = create("EXECUTION_PAYLOAD_HEADER_SCHEMA"); + + public static final SchemaId> + BEACON_STATE_SCHEMA = create("BEACON_STATE_SCHEMA"); + // Altair // Bellatrix diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProviderTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProviderTest.java index 67b6a3d5f5d..54a5196219f 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProviderTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/schemas/registry/BaseSchemaProviderTest.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.spec.schemas.registry; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; @@ -20,73 +21,151 @@ import static tech.pegasys.teku.spec.SpecMilestone.ALTAIR; import static tech.pegasys.teku.spec.SpecMilestone.BELLATRIX; import static tech.pegasys.teku.spec.SpecMilestone.CAPELLA; +import static tech.pegasys.teku.spec.SpecMilestone.DENEB; import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; +import static tech.pegasys.teku.spec.schemas.registry.BaseSchemaProvider.providerBuilder; -import java.util.EnumSet; -import java.util.Set; import org.junit.jupiter.api.Test; import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.schemas.registry.SchemaTypes.SchemaId; class BaseSchemaProviderTest { @SuppressWarnings("unchecked") private static final SchemaId STRING_SCHEMA_ID = mock(SchemaId.class); - private final TestSchemaProvider provider = new TestSchemaProvider(); private final SchemaRegistry mockRegistry = mock(SchemaRegistry.class); @Test - void shouldGetEffectiveMilestone() { - provider.addMilestoneMapping(PHASE0, ALTAIR); + void shouldSupportContinuousUntilHighestMilestone() { + final SchemaProvider provider = + providerBuilder(STRING_SCHEMA_ID) + .withCreator(ALTAIR, (r, c) -> "TestSchemaAltair") + .withCreator(BELLATRIX, (r, c) -> "TestSchemaBellatrix") + .build(); + + assertEquals(ALTAIR, provider.getEffectiveMilestone(ALTAIR)); + assertEquals(BELLATRIX, provider.getEffectiveMilestone(BELLATRIX)); + + when(mockRegistry.getMilestone()).thenReturn(ALTAIR); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaAltair"); + + when(mockRegistry.getMilestone()).thenReturn(BELLATRIX); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaBellatrix"); + + assertThat(provider.getSupportedMilestones()) + .containsAll(SpecMilestone.getAllMilestonesFrom(ALTAIR)); + } + + @Test + void shouldSupportContinuousConstantWithUntil() { + final SchemaProvider provider = + providerBuilder(STRING_SCHEMA_ID) + .constant() + .withCreator(PHASE0, (r, c) -> "TestSchemaPhase0") + .withCreator(BELLATRIX, (r, c) -> "TestSchemaBellatrix") + .until(CAPELLA) + .build(); + assertEquals(PHASE0, provider.getEffectiveMilestone(PHASE0)); assertEquals(PHASE0, provider.getEffectiveMilestone(ALTAIR)); assertEquals(BELLATRIX, provider.getEffectiveMilestone(BELLATRIX)); + assertEquals(BELLATRIX, provider.getEffectiveMilestone(CAPELLA)); + + when(mockRegistry.getMilestone()).thenReturn(PHASE0); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaPhase0"); + + when(mockRegistry.getMilestone()).thenReturn(ALTAIR); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaPhase0"); + + when(mockRegistry.getMilestone()).thenReturn(BELLATRIX); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaBellatrix"); + + when(mockRegistry.getMilestone()).thenReturn(CAPELLA); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaBellatrix"); + + assertThat(provider.getSupportedMilestones()) + .containsExactly(PHASE0, ALTAIR, BELLATRIX, CAPELLA); } @Test - void shouldGetSchema() { + void shouldSupportContinuousDefaultVariable() { + final SchemaProvider provider = + providerBuilder(STRING_SCHEMA_ID) + .withCreator(PHASE0, (r, c) -> "TestSchema" + r.getMilestone()) + .until(CAPELLA) + .build(); + + // variable has effective milestone always equal to the milestone + SpecMilestone.getMilestonesUpTo(CAPELLA) + .forEach(milestone -> assertEquals(milestone, provider.getEffectiveMilestone(milestone))); + when(mockRegistry.getMilestone()).thenReturn(PHASE0); - String result = provider.getSchema(mockRegistry); - assertEquals("TestSchema", result); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaPHASE0"); + + when(mockRegistry.getMilestone()).thenReturn(ALTAIR); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaALTAIR"); + + when(mockRegistry.getMilestone()).thenReturn(BELLATRIX); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaBELLATRIX"); + + when(mockRegistry.getMilestone()).thenReturn(CAPELLA); + assertEquals(provider.getSchema(mockRegistry), "TestSchemaCAPELLA"); + + assertThat(provider.getSupportedMilestones()) + .containsExactly(PHASE0, ALTAIR, BELLATRIX, CAPELLA); } @Test - void shouldGetNonOverlappingVersionMappings() { - provider.addMilestoneMapping(PHASE0, ALTAIR); - provider.addMilestoneMapping(BELLATRIX, CAPELLA); + void shouldThrowWhenNoCreators() { + assertThatThrownBy(() -> providerBuilder(STRING_SCHEMA_ID).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("There should be at least 1 creator"); + } - assertEquals(PHASE0, provider.getEffectiveMilestone(PHASE0)); - assertEquals(PHASE0, provider.getEffectiveMilestone(ALTAIR)); - assertEquals(BELLATRIX, provider.getEffectiveMilestone(BELLATRIX)); - assertEquals(BELLATRIX, provider.getEffectiveMilestone(CAPELLA)); + @Test + void shouldThrowWhenAskingForAnUnsupportedMilestone() { + final SchemaProvider provider = + providerBuilder(STRING_SCHEMA_ID) + .withCreator(ALTAIR, (r, c) -> "TestSchemaAltair") + .until(ALTAIR) + .build(); + + when(mockRegistry.getMilestone()).thenReturn(DENEB); + + assertThatThrownBy(() -> provider.getSchema(mockRegistry)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("It is not supposed to create a specific version for DENEB"); } @Test - void testOverlappingVersionMappingsThrowsException() { - provider.addMilestoneMapping(PHASE0, ALTAIR); + void shouldThrowWhenNotAscendingMilestones() { + assertThatThrownBy( + () -> + providerBuilder(STRING_SCHEMA_ID) + .withCreator(PHASE0, (r, c) -> "TestSchema") + .withCreator(PHASE0, (r, c) -> "TestSchema")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Creator's milestones must added in strict ascending order"); - assertThatThrownBy(() -> provider.addMilestoneMapping(ALTAIR, BELLATRIX)) + assertThatThrownBy( + () -> + providerBuilder(STRING_SCHEMA_ID) + .withCreator(ALTAIR, (r, c) -> "TestSchema") + .withCreator(PHASE0, (r, c) -> "TestSchema")) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Milestone ALTAIR is already mapped to PHASE0"); + .hasMessageStartingWith("Creator's milestones must added in strict ascending order"); } - private static class TestSchemaProvider extends AbstractSchemaProvider { - TestSchemaProvider() { - super(STRING_SCHEMA_ID); - } - - @Override - protected String createSchema( - final SchemaRegistry registry, - final SpecMilestone effectiveMilestone, - final SpecConfig specConfig) { - return "TestSchema"; - } - - @Override - public Set getSupportedMilestones() { - return EnumSet.allOf(SpecMilestone.class); - } + @Test + void shouldThrowWhenWithUntilIsPriorToMilestone() { + assertThatThrownBy( + () -> + providerBuilder(STRING_SCHEMA_ID) + .withCreator(PHASE0, (r, c) -> "TestSchema") + .withCreator(CAPELLA, (r, c) -> "TestSchema") + .until(ALTAIR) + .build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("until must be greater or equal than last creator milestone"); } }