@wundergraph/composition 0.58.2 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1801,6 +1801,12 @@ class NormalizationFactory {
1801
1801
  sizedFields: [],
1802
1802
  requireOneSlicingArgument: true, // per IBM cost spec
1803
1803
  };
1804
+ /**
1805
+ * For each accepted slicing argument, capture the full resolved chain:
1806
+ * (argument + each intermediate input field + leaf).
1807
+ * Later we check the assumedSize against the default value of every element of the chain.
1808
+ */
1809
+ const slicingArgChainByPath = new Map();
1804
1810
  for (const argumentNode of args) {
1805
1811
  const argumentName = argumentNode.name.value;
1806
1812
  // type validation handled upstream
@@ -1830,18 +1836,61 @@ class NormalizationFactory {
1830
1836
  if (valueNode.kind !== graphql_1.Kind.STRING) {
1831
1837
  continue;
1832
1838
  }
1833
- const slicingArgName = valueNode.value;
1834
- const argData = data.argumentDataByName.get(slicingArgName);
1839
+ // slicingArgPath could be just an argument "inputA" or a dot-path "inputA.page.first".
1840
+ const slicingArgPath = valueNode.value;
1841
+ const segments = slicingArgPath.split(string_constants_1.LITERAL_PERIOD);
1842
+ // Reject empty segments (e.g. "", "a.", ".b", "a..b").
1843
+ if (segments.length === 0 || segments.some((s) => s.length === 0)) {
1844
+ errorMessages.push((0, errors_1.listSizeSlicingArgumentMalformedPathErrorMessage)(directiveCoords, slicingArgPath));
1845
+ continue;
1846
+ }
1847
+ // First segment should be valid argument name:
1848
+ const firstSegment = segments[0];
1849
+ const argData = data.argumentDataByName.get(firstSegment);
1835
1850
  if (!argData) {
1836
- errorMessages.push((0, errors_1.listSizeInvalidSlicingArgumentErrorMessage)(directiveCoords, slicingArgName));
1851
+ errorMessages.push((0, errors_1.listSizeInvalidSlicingArgumentErrorMessage)(directiveCoords, slicingArgPath));
1852
+ continue;
1853
+ }
1854
+ /**
1855
+ * Walk the rest of the path in the slicingArgument.
1856
+ * `current` tracks the input value reached so far:
1857
+ * the argument itself for a flat path, or the nested input field for each step of a
1858
+ * dot-path. After the loop it is the leaf, which must be Int/Int!.
1859
+ */
1860
+ let current = argData;
1861
+ const chain = [argData];
1862
+ let isPathInvalid = false;
1863
+ for (let i = 1; i < segments.length; i++) {
1864
+ // Non-leaf step: `current` must unwrap to an Input Object. Lists are rejected here.
1865
+ const unwrapped = current.type.kind === graphql_1.Kind.NON_NULL_TYPE ? current.type.type : current.type;
1866
+ const parentTypeData = this.parentDefinitionDataByTypeName.get(current.namedTypeName);
1867
+ if (unwrapped.kind === graphql_1.Kind.LIST_TYPE ||
1868
+ !parentTypeData ||
1869
+ parentTypeData.kind !== graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION) {
1870
+ errorMessages.push((0, errors_1.listSizeSlicingArgumentSegmentNotInputObjectErrorMessage)(directiveCoords, slicingArgPath, current.name, (0, merge_1.printTypeNode)(current.type)));
1871
+ isPathInvalid = true;
1872
+ break;
1873
+ }
1874
+ const inputValue = parentTypeData.inputValueDataByName.get(segments[i]);
1875
+ if (!inputValue) {
1876
+ errorMessages.push((0, errors_1.listSizeSlicingArgumentSegmentNotFoundErrorMessage)(directiveCoords, slicingArgPath, segments[i], current.namedTypeName));
1877
+ isPathInvalid = true;
1878
+ break;
1879
+ }
1880
+ current = inputValue;
1881
+ chain.push(inputValue);
1882
+ }
1883
+ if (isPathInvalid) {
1837
1884
  continue;
1838
1885
  }
1839
- const unwrappedType = argData.type.kind === graphql_1.Kind.NON_NULL_TYPE ? argData.type.type : argData.type;
1840
- if (unwrappedType.kind === graphql_1.Kind.LIST_TYPE || argData.namedTypeName !== string_constants_1.INT_SCALAR) {
1841
- errorMessages.push((0, errors_1.listSizeSlicingArgumentNotIntErrorMessage)(directiveCoords, slicingArgName, (0, merge_1.printTypeNode)(argData.type)));
1886
+ // Leaf check: the final value must be Int or Int!
1887
+ const unwrappedType = current.type.kind === graphql_1.Kind.NON_NULL_TYPE ? current.type.type : current.type;
1888
+ if (unwrappedType.kind === graphql_1.Kind.LIST_TYPE || current.namedTypeName !== string_constants_1.INT_SCALAR) {
1889
+ errorMessages.push((0, errors_1.listSizeSlicingArgumentNotIntErrorMessage)(directiveCoords, slicingArgPath, (0, merge_1.printTypeNode)(current.type)));
1842
1890
  continue;
1843
1891
  }
1844
- listSizeConfig.slicingArguments.push(slicingArgName);
1892
+ listSizeConfig.slicingArguments.push(slicingArgPath);
1893
+ slicingArgChainByPath.set(slicingArgPath, chain);
1845
1894
  }
1846
1895
  break;
1847
1896
  }
@@ -1897,10 +1946,44 @@ class NormalizationFactory {
1897
1946
  errorMessages.push((0, errors_1.listSizeAssumedSizeWithRequiredSlicingArgumentErrorMessage)(directiveCoords));
1898
1947
  }
1899
1948
  else {
1900
- for (const slicingArgName of listSizeConfig.slicingArguments) {
1901
- const argData = data.argumentDataByName.get(slicingArgName);
1902
- if (argData?.defaultValue) {
1903
- errorMessages.push((0, errors_1.listSizeAssumedSizeSlicingArgDefaultErrorMessage)(directiveCoords, slicingArgName));
1949
+ /**
1950
+ * When assumedSize is set together with slicingArguments, the slicing argument must not have
1951
+ * a default leaf value in any element of the slicingArgument's chain.
1952
+ * That will be checked for all slicing Arguments.
1953
+ * For example, if the query is defined like this:
1954
+ * search(a: SearchInput): [Book]
1955
+ * @listSize(assumedSize: 50, slicingArguments: ["a.b.c"], requireOneSlicingArgument: false)
1956
+ * any of those defaults are forbidden:
1957
+ * input SearchInput { b: PaginationInput = { c: 10 } }
1958
+ * input PaginationInput { c: Int = 10 }
1959
+ * Notably, this query definition is also forbidden with the @listSize above:
1960
+ * search(a: SearchInput = { b: { c: 10 } }): [Book]
1961
+ * A default along the chain only matters if it actually supplies a value for the leaf:
1962
+ * walk the remaining tail through the default's AST and reject only when the walk resolves.
1963
+ * E.g. `a: SearchInput = { b: {} }` is allowed, and the leaf stays undefined.
1964
+ */
1965
+ for (const slicingArgPath of listSizeConfig.slicingArguments) {
1966
+ const chain = slicingArgChainByPath.get(slicingArgPath);
1967
+ if (!chain) {
1968
+ continue;
1969
+ }
1970
+ const segments = slicingArgPath.split(string_constants_1.LITERAL_PERIOD);
1971
+ // Outer loop: which default are we starting from?
1972
+ for (let i = 0; i < chain.length; i++) {
1973
+ let value = chain[i].defaultValue;
1974
+ // Inner loop: try to find a defined leaf in the chain:
1975
+ for (let j = i + 1; j < segments.length; j++) {
1976
+ if (!value || value.kind !== graphql_1.Kind.OBJECT) {
1977
+ value = undefined;
1978
+ break;
1979
+ }
1980
+ const field = value.fields.find((f) => f.name.value === segments[j]);
1981
+ value = field?.value;
1982
+ }
1983
+ if (value !== undefined) {
1984
+ errorMessages.push((0, errors_1.listSizeAssumedSizeSlicingArgDefaultErrorMessage)(directiveCoords, slicingArgPath));
1985
+ break;
1986
+ }
1904
1987
  }
1905
1988
  }
1906
1989
  }