Repository: comigor/artemis Branch: beta Commit: 93a726510197 Files: 154 Total size: 809.4 KB Directory structure: gitextract_38k7t15o/ ├── .devcontainer/ │ ├── Dockerfile │ ├── devcontainer.json │ └── library-scripts/ │ ├── README.md │ └── common-debian.sh ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug-report.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── pull_request.yaml │ └── push.yaml ├── .gitignore ├── CHANGELOG.md ├── FUNDING.yml ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build.yaml ├── example/ │ ├── .gitignore │ ├── README.md │ ├── github/ │ │ ├── .gitignore │ │ ├── build.yaml │ │ ├── lib/ │ │ │ ├── graphql/ │ │ │ │ ├── search_repositories.dart │ │ │ │ ├── search_repositories.graphql │ │ │ │ ├── search_repositories.graphql.dart │ │ │ │ └── search_repositories.graphql.g.dart │ │ │ └── main.dart │ │ └── pubspec.yaml │ ├── graphbrainz/ │ │ ├── build.yaml │ │ ├── lib/ │ │ │ ├── coercers.dart │ │ │ ├── graphbrainz.schema.graphql │ │ │ ├── main.dart │ │ │ └── queries/ │ │ │ ├── ed_sheeran.query.dart │ │ │ ├── ed_sheeran.query.graphql │ │ │ ├── ed_sheeran.query.graphql.dart │ │ │ └── ed_sheeran.query.graphql.g.dart │ │ └── pubspec.yaml │ ├── hasura/ │ │ ├── build.yaml │ │ ├── graphql/ │ │ │ └── messages_with_users.graphql │ │ ├── hasura.sql │ │ ├── lib/ │ │ │ ├── graphql/ │ │ │ │ ├── messages_with_users.graphql.dart │ │ │ │ └── messages_with_users.graphql.g.dart │ │ │ └── main.dart │ │ ├── pubspec.yaml │ │ └── schema.graphql │ └── pokemon/ │ ├── build.yaml │ ├── graphql/ │ │ ├── big_query.query.graphql │ │ ├── fragment_query.query.graphql │ │ ├── fragments_glob.fragment.graphql │ │ ├── fragments_glob.query.graphql │ │ └── simple_query.query.graphql │ ├── lib/ │ │ ├── graphql/ │ │ │ ├── big_query.dart │ │ │ ├── big_query.graphql.dart │ │ │ ├── big_query.graphql.g.dart │ │ │ ├── fragment_query.dart │ │ │ ├── fragment_query.graphql.dart │ │ │ ├── fragment_query.graphql.g.dart │ │ │ ├── fragments_glob.dart │ │ │ ├── fragments_glob.graphql.dart │ │ │ ├── fragments_glob.graphql.g.dart │ │ │ ├── simple_query.dart │ │ │ ├── simple_query.graphql.dart │ │ │ └── simple_query.graphql.g.dart │ │ └── main.dart │ ├── pokemon.schema.graphql │ └── pubspec.yaml ├── lib/ │ ├── artemis.dart │ ├── builder.dart │ ├── client.dart │ ├── generator/ │ │ ├── data/ │ │ │ ├── class_definition.dart │ │ │ ├── class_property.dart │ │ │ ├── data.dart │ │ │ ├── definition.dart │ │ │ ├── enum_definition.dart │ │ │ ├── enum_value_definition.dart │ │ │ ├── fragment_class_definition.dart │ │ │ ├── library_definition.dart │ │ │ ├── nullable.dart │ │ │ ├── query_definition.dart │ │ │ └── query_input.dart │ │ ├── data_printer.dart │ │ ├── ephemeral_data.dart │ │ ├── errors.dart │ │ ├── graphql_helpers.dart │ │ ├── helpers.dart │ │ └── print_helpers.dart │ ├── generator.dart │ ├── schema/ │ │ ├── graphql_query.dart │ │ ├── graphql_response.dart │ │ ├── options.dart │ │ └── options.g2.dart │ ├── transformer/ │ │ └── add_typename_transformer.dart │ └── visitor/ │ ├── canonical_visitor.dart │ ├── generator_visitor.dart │ ├── object_type_definition_visitor.dart │ ├── operation_type_definition_visitor.dart │ ├── schema_definition_visitor.dart │ └── type_definition_node_visitor.dart ├── pubspec.yaml ├── test/ │ ├── generator/ │ │ ├── helpers_test.dart │ │ └── print_helpers_test.dart │ ├── helpers.dart │ └── query_generator/ │ ├── aliases/ │ │ ├── alias_on_leaves_test.dart │ │ └── alias_on_object_test.dart │ ├── append_type_name_test.dart │ ├── ast_schema/ │ │ ├── field_not_found_mutation_test.dart │ │ ├── input_types_test.dart │ │ ├── missing_schema_test.dart │ │ └── multiple_schema_mappint_test.dart │ ├── deprecated/ │ │ ├── deprecated_enum_value_test.dart │ │ ├── deprecated_field_test.dart │ │ ├── deprecated_input_object_field_test.dart │ │ └── deprecated_interface_field_test.dart │ ├── enums/ │ │ ├── enum_duplication_test.dart │ │ ├── enum_list_test.dart │ │ ├── filter_enum_test.dart │ │ ├── input_enum_list_test.dart │ │ ├── input_enum_test.dart │ │ ├── kw_prefix_test.dart │ │ └── query_enum_test.dart │ ├── errors/ │ │ ├── fragment_not_found_test.dart │ │ ├── generation_errors_test.dart │ │ └── root_type_not_found_test.dart │ ├── forwarder_test.dart │ ├── fragments/ │ │ ├── fragment_duplication_test.dart │ │ ├── fragment_glob_schema_level_test.dart │ │ ├── fragment_glob_test.dart │ │ ├── fragment_multiple_queries_test.dart │ │ ├── fragment_on_fragments_test.dart │ │ ├── fragments_multiple_test.dart │ │ ├── fragments_test.dart │ │ └── multiple_references_on_simple_naming_test.dart │ ├── interfaces/ │ │ ├── interface_fragment_glob_test.dart │ │ ├── interface_possible_types_test.dart │ │ └── interface_test.dart │ ├── multiple_operations_per_file_test.dart │ ├── multiple_queries_test.dart │ ├── mutations_and_inputs/ │ │ ├── complex_input_objects_test.dart │ │ ├── custom_scalars_on_input_objects_test.dart │ │ ├── filter_input_objects_test.dart │ │ ├── input_duplication_test.dart │ │ ├── mutations_test.dart │ │ ├── non_nullable_list_inputs_test.dart │ │ └── recursive_input_test.dart │ ├── naming/ │ │ ├── casing_conversion_test.dart │ │ ├── common.dart │ │ ├── pathed_with_fields_test.dart │ │ └── simple_naming_test.dart │ ├── nnbd_test.dart │ ├── query_generator_test.dart │ ├── scalars/ │ │ ├── custom_scalars_test.dart │ │ ├── scalars_test.dart │ │ └── unused_custom_scalars_test.dart │ ├── subscription_test.dart │ └── union/ │ ├── union_types_test.dart │ └── union_with_nested_types_test.dart └── tool/ └── fetch_schema.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainer/Dockerfile ================================================ # Update VARIANT in devcontainer.json to pick a Dart version ARG VARIANT=2 FROM google/dart:${VARIANT} # [Option] Install zsh ARG INSTALL_ZSH="true" # [Option] Upgrade OS packages to their latest versions ARG UPGRADE_PACKAGES="false" # Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. ARG USERNAME=vscode ARG USER_UID=1000 ARG USER_GID=$USER_UID COPY library-scripts/*.sh /tmp/library-scripts/ RUN apt-get update \ && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts # Add bin location to path ENV PUB_CACHE="/usr/local/share/pub-cache" ENV PATH="${PATH}:${PUB_CACHE}/bin" RUN mkdir -p ${PUB_CACHE} \ && chown ${USERNAME}:root ${PUB_CACHE} \ && echo "if [ \"\$(stat -c '%U' ${PUB_CACHE})\" != \"${USERNAME}\" ]; then sudo chown -R ${USER_UID}:root ${PUB_CACHE}; fi" \ | tee -a /root/.bashrc /root/.zshrc /home/${USERNAME}/.bashrc >> /home/${USERNAME}/.zshrc # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends ================================================ FILE: .devcontainer/devcontainer.json ================================================ { "name": "Dart", "build": { "dockerfile": "Dockerfile", // Update VARIANT to pick a Dart version "args": { "VARIANT": "2", }, }, // Set *default* container specific settings.json values on container create. "settings": { "editor.formatOnSave": true, "terminal.integrated.shell.linux": "/usr/bin/zsh", "editor.fontLigatures": true, "workbench.colorTheme": "Default Dark+", "editor.fontFamily": "'Jetbrains Mono', Menlo, Monaco, 'Courier New', monospace", }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "dart-code.dart-code", "dart-code.flutter", "redhat.vscode-yaml", "luanpotter.dart-import", "jeroen-meijer.pubspec-assist", "kumar-harsh.graphql-for-vscode", ], // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "uname -a", // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. // "remoteUser": "vscode" } ================================================ FILE: .devcontainer/library-scripts/README.md ================================================ # Warning: Folder contents may be replaced The contents of this folder will be automatically replaced with a file of the same name in the [vscode-dev-containers](https://github.com/microsoft/vscode-dev-containers) repository's [script-library folder](https://github.com/microsoft/vscode-dev-containers/tree/master/script-library) whenever the repository is packaged. To retain your edits, move the file to a different location. You may also delete the files if they are not needed. ================================================ FILE: .devcontainer/library-scripts/common-debian.sh ================================================ #!/usr/bin/env bash #------------------------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- # # Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/common.md # # Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My *! flag] INSTALL_ZSH=${1:-"true"} USERNAME=${2:-"automatic"} USER_UID=${3:-"automatic"} USER_GID=${4:-"automatic"} UPGRADE_PACKAGES=${5:-"true"} INSTALL_OH_MYS=${6:-"true"} set -e if [ "$(id -u)" -ne 0 ]; then echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' exit 1 fi # If in automatic mode, determine if a user already exists, if not use vscode if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then USERNAME="" POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") for CURRENT_USER in ${POSSIBLE_USERS[@]}; do if id -u ${CURRENT_USER} > /dev/null 2>&1; then USERNAME=${CURRENT_USER} break fi done if [ "${USERNAME}" = "" ]; then USERNAME=vscode fi elif [ "${USERNAME}" = "none" ]; then USERNAME=root USER_UID=0 USER_GID=0 fi # Load markers to see which steps have already run MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" if [ -f "${MARKER_FILE}" ]; then echo "Marker file found:" cat "${MARKER_FILE}" source "${MARKER_FILE}" fi # Ensure apt is in non-interactive to avoid prompts export DEBIAN_FRONTEND=noninteractive # Function to call apt-get if needed apt-get-update-if-needed() { if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then echo "Running apt-get update..." apt-get update else echo "Skipping apt-get update." fi } # Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then apt-get-update-if-needed PACKAGE_LIST="apt-utils \ git \ openssh-client \ gnupg2 \ iproute2 \ procps \ lsof \ htop \ net-tools \ psmisc \ curl \ wget \ rsync \ ca-certificates \ unzip \ zip \ nano \ vim-tiny \ less \ jq \ lsb-release \ apt-transport-https \ dialog \ libc6 \ libgcc1 \ libgssapi-krb5-2 \ libicu[0-9][0-9] \ liblttng-ust0 \ libstdc++6 \ zlib1g \ locales \ sudo \ ncdu \ man-db" # Install libssl1.1 if available if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then PACKAGE_LIST="${PACKAGE_LIST} libssl1.1" fi # Install appropriate version of libssl1.0.x if available LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then # Debian 9 PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.2" elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then # Ubuntu 18.04, 16.04, earlier PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.0" fi fi echo "Packages to verify are installed: ${PACKAGE_LIST}" apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) PACKAGES_ALREADY_INSTALLED="true" fi # Get to latest versions of all packages if [ "${UPGRADE_PACKAGES}" = "true" ]; then apt-get-update-if-needed apt-get -y upgrade --no-install-recommends apt-get autoremove -y fi # Ensure at least the en_US.UTF-8 UTF-8 locale is available. # Common need for both applications and things like the agnoster ZSH theme. if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen locale-gen LOCALE_ALREADY_SET="true" fi # Create or update a non-root user to match UID/GID. if id -u ${USERNAME} > /dev/null 2>&1; then # User exists, update if needed if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -G $USERNAME)" ]; then groupmod --gid $USER_GID $USERNAME usermod --gid $USER_GID $USERNAME fi if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then usermod --uid $USER_UID $USERNAME fi else # Create user if [ "${USER_GID}" = "automatic" ]; then groupadd $USERNAME else groupadd --gid $USER_GID $USERNAME fi if [ "${USER_UID}" = "automatic" ]; then useradd -s /bin/bash --gid $USERNAME -m $USERNAME else useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME fi fi # Add add sudo support for non-root user if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME chmod 0440 /etc/sudoers.d/$USERNAME EXISTING_NON_ROOT_USER="${USERNAME}" fi # ** Shell customization section ** if [ "${USERNAME}" = "root" ]; then USER_RC_PATH="/root" else USER_RC_PATH="/home/${USERNAME}" fi # .bashrc/.zshrc snippet RC_SNIPPET="$(cat << EOF export USER=\$(whoami) export PATH=\$PATH:\$HOME/.local/bin EOF )" # code shim, it fallbacks to code-insiders if code is not available cat << 'EOF' > /usr/local/bin/code #!/bin/sh get_in_path_except_current() { which -a "$1" | grep -v "$0" | head -1 } code="$(get_in_path_except_current code)" if [ -n "$code" ]; then exec "$code" "$@" elif [ "$(command -v code-insiders)" ]; then exec code-insiders "$@" else echo "code or code-insiders is not installed" >&2 exit 127 fi EOF chmod +x /usr/local/bin/code # Codespaces themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme CODESPACES_BASH="$(cat \ <&1 echo -e "$(cat "${TEMPLATE}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${USER_RC_FILE} if [ "${OH_MY}" = "bash" ]; then sed -i -e 's/OSH_THEME=.*/OSH_THEME="codespaces"/g' ${USER_RC_FILE} mkdir -p ${OH_MY_INSTALL_DIR}/custom/themes/codespaces echo "${CODESPACES_BASH}" > ${OH_MY_INSTALL_DIR}/custom/themes/codespaces/codespaces.theme.sh else sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${USER_RC_FILE} mkdir -p ${OH_MY_INSTALL_DIR}/custom/themes echo "${CODESPACES_ZSH}" > ${OH_MY_INSTALL_DIR}/custom/themes/codespaces.zsh-theme fi # Shrink git while still enabling updates cd ${OH_MY_INSTALL_DIR} git repack -a -d -f --depth=1 --window=1 if [ "${USERNAME}" != "root" ]; then cp -rf ${USER_RC_FILE} ${OH_MY_INSTALL_DIR} /root chown -R ${USERNAME}:${USERNAME} ${USER_RC_PATH} fi } if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then echo "${RC_SNIPPET}" >> /etc/bash.bashrc RC_SNIPPET_ALREADY_ADDED="true" fi install-oh-my bash bashrc.osh-template https://github.com/ohmybash/oh-my-bash # Optionally install and configure zsh and Oh My Zsh! if [ "${INSTALL_ZSH}" = "true" ]; then if ! type zsh > /dev/null 2>&1; then apt-get-update-if-needed apt-get install -y zsh fi if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then echo "${RC_SNIPPET}" >> /etc/zsh/zshrc ZSH_ALREADY_INSTALLED="true" fi install-oh-my zsh zshrc.zsh-template https://github.com/ohmyzsh/ohmyzsh fi # Write marker file mkdir -p "$(dirname "${MARKER_FILE}")" echo -e "\ PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" echo "Done!" ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Before reporting a bug, please test the beta branch!** ## Bug description Please describe what happened wrong and the expected behaviour/output. ## Specs Artemis version: [e.g. 6.0.3-beta.1]
build.yaml: ```yaml # Please paste your `build.yaml` file ```
Artemis output: ```bash # Please paste the output of $ flutter pub run build_runner build --verbose #or $ pub run build_runner build --verbose ```
GraphQL schema: ```graphql # If possible, please paste your GraphQL schema file, # or a minimum reproducible schema of the bug. ```
GraphQL query: ```graphql # If possible, please paste your GraphQL query file, # or a minimum reproducible query of the bug. ```
================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ **Make sure you're opening this pull request pointing to beta branch!** ## What does this PR do/solve? ================================================ FILE: .github/workflows/pull_request.yaml ================================================ on: pull_request name: CI jobs: check-version-and-changelog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: comigor/actions/check-version-and-changelog@master with: repo_token: ${{ github.token }} base_ref: ${{ github.base_ref }} lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Check formatting if: always() && steps.install.outcome == 'success' run: dart format --set-exit-if-changed . test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Test if: always() && steps.install.outcome == 'success' run: dart test analyze: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Analyze if: always() && steps.install.outcome == 'success' run: dart analyze ================================================ FILE: .github/workflows/push.yaml ================================================ on: push: branches: - master - beta name: CI jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Check formatting if: always() && steps.install.outcome == 'success' run: dart format --set-exit-if-changed . test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Test if: always() && steps.install.outcome == 'success' run: dart test analyze: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Analyze if: always() && steps.install.outcome == 'success' run: dart analyze create-tag-and-release: needs: - lint - test - analyze runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/beta' steps: - uses: actions/checkout@master - id: check_version_and_changelog name: Check if version on pubspec.yaml was changed and if there's an entry for this new version on CHANGELOG uses: comigor/actions/check-version-and-changelog@master with: base_ref: "${{ github.ref }}" - name: Push tag uses: anothrNick/github-tag-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CUSTOM_TAG: "v${{ steps.check_version_and_changelog.outputs.package_version }}" - name: Create release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: "v${{ steps.check_version_and_changelog.outputs.package_version }}" release_name: "Release v${{ steps.check_version_and_changelog.outputs.package_version }}" deploy: needs: create-tag-and-release runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/beta' steps: - uses: actions/checkout@master - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: install name: Install dependencies run: dart pub get - name: Publish to pub.dev run: | mkdir -p ~/.config/dart echo '${{ secrets.PUB_CREDENTIALS }}' > ~/.config/dart/pub-credentials.json dart pub publish --force ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/dart,macos ### Dart ### # See https://www.dartlang.org/guides/libraries/private-files # Files and directories created by pub .dart_tool/ .packages .fvm build/ # If you're building an application, you may want to check-in your pubspec.lock /pubspec.lock # Directory created by dartdoc # If you don't generate documentation locally you can remove this line. doc/api/ # Avoid committing generated Javascript files: *.dart.js *.info.json # Produced by the --dump-info flag. *.js # When generated by dart2js. Don't specify *.js if your # project includes source files written in JavaScript. *.js_ *.js.deps *.js.map ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # End of https://www.gitignore.io/api/dart,macos .idea ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG ## 7.13.1 - Move `beta` version out of beta for pub.dev awareness ## 7.13.0-beta.3 - Add discontinuation notice ## 7.13.0-beta.2 - readme fix ## 7.13.0-beta.1 - package update ## 7.12.0-beta.1 - package update ## 7.11.0-beta.1 - actions/checkout@v3 update ## 7.11.0-beta - package update ## 7.10.0-beta.4 - fix pipeline quoting issues ## 7.10.0-beta.3 - fix pipeline path, again ## 7.10.0-beta.2 - fix pipeline path ## 7.10.0-beta.1 - update workflows to use official community actions ## 7.10.0-beta - package update ## 7.9.0-beta - common fragments overlap fix ## 7.8.0-beta - package update ## 7.7.0-beta - package update ## 7.6.1-beta - operation name constant ## 7.6.2-beta - generate_queries flag to signify if query documents and operation names should be generated ## 7.6.1-beta - operation name constant ## 7.6.0-beta - package updates ## 7.5.0-beta - there is no need to use `fragments_glob` any more just specify the glob which will include all your graphql docs ## 7.4.0-beta - allow using file fragments and common fragments at the same time ## 7.3.3-beta - fix https://github.com/comigor/artemis/issues/373 ## 7.3.2-beta - performance improvements-2 ## 7.3.1-beta - performance improvements ## 7.3.0-beta - package update ## 7.2.6-beta - Fix README ## 7.2.5-beta - Format some files to improve `pana` score ## 7.2.4-beta - package update ## 7.2.3-beta - Update examples and make sure they all work ## 7.2.2-beta - Add throwable errors for missing root object and used query fragments ## 7.2.1-beta - Update package to use official Dart lint following [this guide](https://github.com/dart-lang/lints#migrating-from-packagepedantic) ## 7.2.0-beta.0 - package update ## 7.1.1-beta.1 - support of `fragments_glob` at schema level ## 7.1.0-beta.2 - fix for https://github.com/comigor/artemis/issues/341 ## 7.1.0-beta.1 - duplicated $$typename fix ## 7.1.0-beta.0 **BREAKING CHANGE** - changed the naming of scalar mappers ## 7.0.0-beta.17 - example indentaion fix ## 7.0.0-beta.16 - lazy canonical visitors ## 7.0.0-beta.15 - Update build_runner version in examples - Update Pokemon API in pokemon example - Update WebSocketLink in hasura example ## 7.0.0-beta.14 - Support gql context in client's execute and stream methods ## 7.0.0-beta.13 - fix for https://github.com/comigor/artemis/issues/177 ## 7.0.0-beta.12 - package update ## 7.0.0-beta.11 - document generation fix for https://github.com/comigor/artemis/issues/307 ## 7.0.0-beta.10 - union generation fix for https://github.com/comigor/artemis/issues/284 ## 7.0.0-beta.9 - nullable scalar_mapping types ## 7.0.0-beta.8 - packages update ## 7.0.0-beta.7 - config file error handling ## 7.0.0-beta.6 - packages update ## 7.0.0-beta.5 - github example fix - pokemon example fix - hasura example fix - graphbrainz example fix ## 7.0.0-beta.4 - packages update - null-safe tests ## 7.0.0-beta.3 - document generated in separate variable for easier usage ## 7.0.0-beta.2 - package nnbd migraion ## 7.0.0-beta.1 **MAJOR BREAKING CHANGE** - Code generated by Artemis is now nnbd-compliant ## 6.20.1-beta.2 - Add codespaces folder ## 6.20.1-beta.1 - Allow for auto generated response and inputs to extend JsonSerializable ## 6.19.3-beta.1 - bugfix of append typename - common fragments ## 6.19.2-beta.1 - bugfix of append typename ## 6.19.1-beta.1 - Append typename flag ## 6.18.2 - Merging beta into master ## 6.18.1-beta.1 - add unknownEnumValue on list enums https://github.com/comigor/artemis/issues/246 ## 6.17.3 - Add test case, warning to README, fix CI pipeline ## 6.17.2 - Update dependencies versions ## 6.17.1-beta.1 - package updates and one test fix ## 6.16.1-beta.1 - simple naming schema fix https://github.com/comigor/artemis/issues/226 ## 6.15.1-beta.1 - Override annotation fix ## 6.14.1-beta.1 - Package updates ## 6.13.1-beta.1 - input underscore bugfix https://github.com/comigor/artemis/issues/223 ## 6.12.3-beta.2 - Subscription test added ## 6.12.3-beta.1 - Readme fix ## 6.12.2-beta.1 - Fixed `ignore_for_file` documentation. ## 6.12.1-beta.1 - Added `ignore_for_file` option to ignore linter rules on generated files. ## 6.11.1-beta.1 - improved canonical types handling ## 6.10.1-beta.1 - Package updates ## 6.9.2-beta.1 - Fixed `toJson() doesn't remove "kw$" prefix` ## 6.8.2-beta.1 - test fix ## 6.8.1-beta.1 - fix for multiple schema_mapping ## 6.7.2-beta.1 - analyzer and linter warnings fix ## 6.7.1-beta.1 - uppercase keyword fix ## 6.6.4-beta.1 - pubspec fix ## 6.6.3-beta.1 - test fix ## 6.6.2-beta.1 - nnbd preparation ## 6.6.1-beta.1 - allow multiple operations per file ## 6.5.2-beta.1 - performance improvements - scan schema for canonical types only once ## 6.5.1-beta.1 - enum name pascal casing. ## 6.5.0-beta.1 - Add deprecated annotations in fields. ## 6.4.4-beta.1 - Build type name recursively, considering casing changes. ## 6.4.3-beta.1 - Mass package update ## 6.3.3-beta.1 - Centralize naming transformations; make types PascalCase and fields camelCase. ## 6.3.2-beta.1 - Recursively consider input lists. ## 6.3.1-beta.1 - Do not throw on unused scalars. ## 6.3.0-beta.1 **MAJOR BREAKING CHANGE** - all starting underscores are replaced with $ - `__typename` field replaced with `$$typename` - enums are named according to Dart spec - fields similar to Dart keywords are prefixed with `kw$` ## 6.2.1-beta.1 - Check for more error causes and throw, explaining the error. ## 6.2.0-beta.1 **MAJOR BREAKING CHANGE** We've found a regression on `6.1.0-beta.1`, which sends Enums as camelCase to the server, when they should be sent as SCREAMING_SNAKE_CASE. - Reverts `6.1.0-beta.1`. ## 6.1.1-beta.2 - Improve actions and check pipeline output. ## 6.1.1-beta.1 - Short-circuit input object generation on recursive detection ## 6.1.0-beta.1 **MAJOR BREAKING CHANGE** - Convert enum casing to camel case. ## 6.0.11-beta.1 - Convert `ClassProperty` annotation item to `List`. ## 6.0.10-beta.1 - Duplication bug fix ## 6.0.9-beta.1 - Added the exception for the case when `fragment_glob` leads to query files fragments ignore. ## 6.0.8-beta.1 - Adapt Artemis to subscriptions and create an example ## 6.0.7-beta.1 - Fix for the interfaces which uses fragments from fragments_glob ## 6.0.6-beta.1 - Hide build logs under `--verbose` flag ## 6.0.5-beta.1 - Include coercers annotations on custom scalars on input objects. ## 6.0.4-beta.1 - Properly consider "sub-fragments" on class generation. ## 6.0.3-beta.1 - Fix generation of custom scalars and its functions. ## 6.0.2-beta.1 - Fix invalid reference to class on Query generations. ## 6.0.1-beta.1 - End forwarder file with a newline. ## 6.0.0-beta.1 **MAJOR BREAKING CHANGE** - Generate canonical objects (enums and input objects) with their original names on GraphQL. Fragments are also generated with their own names (plus the `Mixin` prefix, for now). - Make it possible to select a naming scheme to be used for generate the class names. `pathedWithTypes` is the default for retrocompatibility, where the names of previous types are used as prefix of the next class. This can generate duplication on certain schemas. With `pathedWithFields`, the names of previous fields are used as prefix of the next class and with `simple`, only the actual GraphQL class nameis considered. See discussion on [#90][pr-90] and [#96][pr-96] for more information. ## 5.1.0 - Add `.graphql.` to outputted files path, in a non-breaking change way: a "forwarder" file will be generated to make it retro-compatible when a configurated output doesn't end with `.graphql.dart`. ## 5.0.4 - Update CI to include beta branch. ## 5.0.3 - Update examples to match latest changes. ## 5.0.2 - Use default names for query/mutation root when SDL does not declare `schema`. ## 5.0.1 - Fix generation of recursive input objects introduced by 5.0.0. ## 5.0.0 **MAJOR BREAKING CHANGE** In this version we moved from `json` to `graphql` (SDL) schema parsing. This allowed us to get rid off ±1200 lines of code which makes the project support much easier. The test files with schema definitions became more clear and human readable. If you already have your schema in SDL format, just point to it in `build.yaml`. If not, use this [snippet][introspection-to-sdl-snippet] (from [this Apollo article][apollo-3-ways-schema]) or online helpers like [this one][introspection-to-sdl-online] to convert from one to another. ## 4.0.2 - Only add unknownEnumValue on non-list enums - Consider all classes to include reference to meta package ## 4.0.1 - Look at mutation root when generating a mutation ## 4.0.0 **MAJOR BREAKING CHANGE** This version completely refactors how Artemis generate code (by finally using the implementation of visitor pattern provided by `gql`). On top of that, I've decided to do other major breaking changes to make code cleaner and more maintainable. Listed: - `add_query_prefix` doesn't exist anymore (it's now the default to generate classes with its "path" from the query), e.g., this query's `city` field will be typed as `CityName$QueryRoot$User$Address$City`: ```graphql query city_name { user { address { city { name } } } } ``` This change was also done to tip users to NOT use those generated queries directly on their code, to avoid coupling them to your business logic. - `custom_parser_import` was moved to inside a ScalarMap, and `use_custom_parser` was removed. - `resolve_type_field` option was renamed to `type_name_field`, as `__typename` is the correct field name (by GraphQL spec). - Classes generated for mutation will have a `Mutation` suffix, as queries already have `Query` suffix. - Change pre-generation data classes constructors to named parameters, so if you're using `GraphQLQueryBuilder.onBuild`, it will break. And also: - Add more logs and errors while generating code, to help debugging. - Add more/refactor tests. - Add a GitHub example. TODO: - [ ] re-add more logs - [ ] clean options (?) - [ ] prefix every class with `$` (?) - [ ] refactor class naming variables - [ ] review readme and changelog ## 3.2.1 - Fix unknown enum: add prefix ## 3.2.0 - Make enums loose. When unknown values are provided into an enum, it will fall back to a custom `ARTEMIS_UNKNOWN` value avoiding breaking/crashing the client. ## 3.1.0 - Allow to dispose `ArtemisClient` underlining http client when possible ## 3.0.0 - BREAKING: Marks non nullable input field as `@required` [#68][pr-68] ## 2.2.2 - Make lists as input objects work again ## 2.2.1 - Display error on types not found on schema ## 2.2.0+1 - Add "Articles and videos" category on README ## 2.2.0 - Share fragments between queries and schemas (see `fragments_glob`) [#65][pr-65] ## 2.1.4 - Add missing prefix to generated enums ## 2.1.3 - Bump equatable/gql suite, refine GitHub actions ## 2.1.2 - Bump json_serializable/json_annotation ## 2.1.1 - Properly consider Union types on generation ## 2.1.0+1 - Fix GitHub actions deploy pipeline - Make sure artemis depends on json_annotation ## 2.1.0 - Generate fragments as mixins ## 2.0.7+1 - README updates ## 2.0.7 - Add missing prefix to interfaces ## 2.0.6 - Perserve the query name casing ## 2.0.5 - Bump `gql` package ## 2.0.4 - Bump `gql` package ## 2.0.3 - Generate every field of input objects ## 2.0.2 - Support `__schema` key under the data field or on root of `schema.json`. ## 2.0.1 - Loosen up dependencies to make it work again with Flutter `beta` channel ## 2.0.0 - BREAKING: move `GraphQLError` to `package:gql`. If you don't use it, or just reference it indirectly, it will not be breaking, but a major will be bumped anyway, just for sure. - Upgrade `package:gql` to version `0.7.4` - Build GQL AST into generated Dart code instead of the raw string - Use `Link` from `package:gql/link` as the execution interface of `ArtemisClient` - Use `package:gql_dedupe_link` and `package:gql_http_link` as the default links ## 1.0.4 - Add a test to guarantee query inputs can be lists ## 1.0.3 - Disable implicit casts - Avoid double-parsing the source string ## 1.0.2 - Differentiate lists from named types when looping through variables - Consider nullable operation name when defining query name ## 1.0.1 - Upgrade `gql` to version `0.2.0` to get rid of direct dependency on `source_span` and for better parsing errors. - Filter for `SchemaMap` with `output` when generating code ## 1.0.0 - Breaking: Add required `output` option to `SchemaMap` - Make Artemis a `$lib$` synthetic generator - Add `add_query_prefix` option to `SchemaMap` ## 0.7.0 - Make generated classes a mixin of `Equatable`, meaning they can be easily comparable with `==` ## 0.6.1 - Include pubspec.lock files of examples ## 0.6.0 - Replace `graphql_parser` with `gql` package ## 0.5.1 - Add most documentation - Increase pana grade (health and maintenance) - Fix some stuff related to importing http on client ## 0.5.0 - Start using `code_builder` to better generate Dart code ## 0.4.0 - Allow scalar mappings to include imports for types ## 0.3.2 - Decode HTTP response as UTF-8 on execute helper. ## 0.3.1 - Export common used files on default package route (`package:artemis/artemis.dart`) - Use single schemaMap globbing stream to make sure only one schema will be found - Add missing changelog - Test new github actions ## 0.3.0 BREAKING - Add new generators to GraphQLQuery and QueryArguments - Fix toJson() on JsonSerializable classes (for nested entities) - [BREAKING] Remove the `execute*` functions generations, to use instead the generic `ArtemisClient` class that should receive a GraphQLQuery generated subclass. ## 0.2.1 Set HTTP headers only when using default HTTP client. ## 0.2.0 BREAKING Completely overhaul how this works. Artemis won't generate a full schema typing anymore. Instead, it will use the schema to generate typings from a specific query or mutation. It will also create helper functions to execute those queries. See [README][readme] for more info. This is totally a breaking change but as this library is still on alpha, I should keep it under 1.0. ## 0.1.3 - Make objects that implement interfaces override resolveType ## 0.1.2 - Improve package score ## 0.1.1 - Enable tests on pipeline ## 0.1.0 - "Fix" json_serializable dependency - Add tests - Generate union types as inheritance - Generate interface types as implementation - Make generated code choose inheritance ## 0.0.1 - First release - No tests - No documentation - Parse complex GraphQL schemas (incorrectly, now I know) - Parse all GraphQL types types (union, interface, enum, input object, object, scalar, list, non null) - Consider custom scalars - Not even compile from scratch - Lot of bugs [readme]: ./README.md [pr-65]: https://github.com/comigor/artemis/pull/65 [pr-68]: https://github.com/comigor/artemis/pull/68 [apollo-3-ways-schema]: https://blog.apollographql.com/three-ways-to-represent-your-graphql-schema-a41f4175100d#:~:text=Introspection%20query%20result%20to%20SDL [introspection-to-sdl-snippet]: https://gist.github.com/stubailo/041999ba5b8b15cede60b93ff9a38f53 [introspection-to-sdl-online]: https://codesandbox.io/s/graphql-introspection-sdl-svlx2 [pr-90]: https://github.com/comigor/artemis/pull/90 [pr-96]: https://github.com/comigor/artemis/pull/96 ================================================ FILE: FUNDING.yml ================================================ github: comigor patreon: comigor custom: ['https://www.blockchain.com/btc/payment_request?address=1M1NJGg4SE7eaGUPUxQmqT9U1NhNYzW22J', 'https://api.qrserver.com/v1/create-qr-code/?data=xrb%3Anano_1jbe6nohyhpswj8gea8d7bpdd1ixxcmf3haj7gcrwzfweiwrmwuh33gpu1q3'] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Igor Borges Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Artemis

## Notice Artemis has been [discontinued](https://github.com/gql-dart/ferry/issues/541). For alternatives, take a look at the [Ferry](https://pub.dev/packages/ferry) project, featuring code generation for data types, customizable network layer (using `gql_link`), cache, data store connections, refetching + pagination support and more. Also check out [graphql](https://pub.dev/packages/graphql), with features closely matching the current Artemis state: fully-featured client (for Dart and Flutter) with persistence, type generation (with [graphql_codegen](https://pub.dev/packages/graphql_codegen)), cache, and more. Users and community are also invited to join the [GraphQL Dart](https://discord.gg/Pu8AMajSd) Discord to discuss migration approaches and ask for support. Thanks to all contributors and users that made this project possible.
To check out the old README, click here. [![View at pub.dev][pub-badge]][pub-link] [![Test][actions-badge]][actions-link] [![PRs Welcome][prs-badge]][prs-link] [![Star on GitHub][github-star-badge]][github-star-link] [![Fork on GitHub][github-forks-badge]][github-forks-link] [![Discord][discord-badge]][discord-link] [pub-badge]: https://img.shields.io/pub/v/artemis?style=for-the-badge [pub-link]: https://pub.dev/packages/artemis [actions-badge]: https://img.shields.io/github/workflow/status/comigor/artemis/test?style=for-the-badge [actions-link]: https://github.com/comigor/artemis/actions [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge [prs-link]: https://github.com/comigor/artemis/issues [github-star-badge]: https://img.shields.io/github/stars/comigor/artemis.svg?style=for-the-badge&logo=github&logoColor=ffffff [github-star-link]: https://github.com/comigor/artemis/stargazers [github-forks-badge]: https://img.shields.io/github/forks/comigor/artemis.svg?style=for-the-badge&logo=github&logoColor=ffffff [github-forks-link]: https://github.com/comigor/artemis/network/members [discord-badge]: https://img.shields.io/discord/559455668810153989.svg?style=for-the-badge&logo=discord&logoColor=ffffff [discord-link]: https://discord.gg/2Y4wdE4 Check the [**beta**](https://github.com/comigor/artemis/tree/beta) branch for the bleeding edge (and breaking) stuff. Artemis is a code generator that looks for `schema.graphql` (GraphQL SDL - Schema Definition Language) and `*.graphql` files and builds `.graphql.dart` files typing that query, based on the schema. That's similar to what [Apollo](https://github.com/apollographql/apollo-client) does (Artemis is his sister anyway). --- ## **Installation** Add the following to your `pubspec.yaml` file to be able to do code generation: ```yaml dev_dependencies: artemis: '>=7.0.0 <8.0.0' build_runner: ^2.1.4 json_serializable: ^6.0.1 ``` The generated code uses the following packages in run-time: ```yaml dependencies: artemis: '>=8.0.0 <8.0.0' # only if you're using ArtemisClient! json_annotation: ^4.3.0 equatable: ^2.0.3 gql: ^0.13.1-alpha ``` Then run: ```shell dart pub get ``` or ```shell flutter pub get ``` Now Artemis will generate the API files for you by running: ```shell dart run build_runner build ``` or ```shell flutter pub run build_runner build ``` ## **Configuration** Artemis offers some configuration options to generate code. All options should be included on `build.yaml` file on the root of the project: ```yaml targets: $default: builders: artemis: options: # custom configuration options! ``` > ⚠️ Make sure your configuration file is called `build.yaml` (with `.yaml` extension, not `.yml`)! | Option | Default value | Description | | - | - | - | | `generate_helpers` | `true` | If Artemis should generate query/mutation helper GraphQLQuery subclasses. | | `scalar_mapping` | `[]` | Mapping of GraphQL and Dart types. See [Custom scalars](#custom-scalars). | | `schema_mapping` | `[]` | Mapping of queries and which schemas they will use for code generation. See [Schema mapping](#schema-mapping). | | `fragments_glob` | `null` | Import path to the file implementing fragments for all queries mapped in schema_mapping. If it's assigned, fragments defined in schema_mapping will be ignored. | | `ignore_for_file` | `[]` | The linter rules to ignore for artemis generated files. | | `generate_queries` | `true` | If Artemis should generate query documents and operation names. If you are using Artemis with `graphql` library it is useful to have those queries and operation names generated but without Atremis specific classes to exclude Artemis from dependancies | It's important to remember that, by default, [build](https://github.com/dart-lang/build) will follow [Dart's package layout conventions](https://dart.dev/tools/pub/package-layout), meaning that only some folders will be considered to parse the input files. So, if you want to reference files from a folder other than `lib/`, make sure you've included it on `sources`: ```yaml targets: $default: sources: - lib/** - graphql/** - data/** - schema.graphql ``` ### **Schema mapping** By default, Artemis won't generate anything. That's because your queries/mutations should be linked to GraphQL schemas. To configure it, you need to point a `schema_mapping` to the path of those queries and schemas: ```yaml targets: $default: builders: artemis: options: schema_mapping: - output: lib/graphql_api.graphql.dart schema: lib/my_graphql_schema.graphql queries_glob: lib/**.graphql ``` Each `SchemaMap` is configured this way: | Option | Default value | Description | | - | - | - | | `output` | | Relative path to output the generated code. It should end with `.graphql.dart` or else the generator will need to generate one more file. | | `schema` | | Relative path to the GraphQL schema. | | `queries_glob` | | Glob that selects all query files to be used with this schema. | | `naming_scheme` | `pathedWithTypes` | The naming scheme to be used on generated classes names. `pathedWithTypes` is the default for retrocompatibility, where the names of previous types are used as prefix of the next class. This can generate duplication on certain schemas. With `pathedWithFields`, the names of previous fields are used as prefix of the next class and with `simple`, only the actual GraphQL class nameis considered. | | `type_name_field` | `__typename` | The name of the field used to differentiate interfaces and union types (commonly `__typename` or `__resolveType`). Note that `__typename` field are not added automatically to the query. If you want interface/union type resolution, you need to manually add it there or set `append_type_name` to `true`. | | `append_type_name` | `false` | Appends `type_name_field` value to the query selections set. | | `fragments_glob` | `null` | Import path to the file implementing fragments for all queries mapped in schema_mapping. | See [examples](./example) for more information and configuration options. ### **Custom scalars** If your schema uses custom scalars, they must be defined on `build.yaml`. If it needs a custom parser (to decode from/to json), the `custom_parser_import` path must be set and the file must implement both `fromGraphQL___ToDart___` and `fromDart___ToGraphQL___` constant functions. `___ToDart___` and `___ToGraphQL___` should be named including nullability, here is an example: * `file: Upload` => `fromGraphQLUploadNullableToDartMultipartFileNullable` and `fromDartMultipartFileNullableToGraphQLUploadNullable` * `file: Upload!` => `fromGraphQLUploadToDartMultipartFile` and `fromDartMultipartFileToGraphQLUpload` * `file: [Upload]` => `fromGraphQLListNullableUploadNullableToDartListNullableMultipartFileNullable` and `fromDartListNullableMultipartFileNullableToGraphQLListNullableUploadNullable` * `file: [Upload]!` => `fromGraphQLListUploadNullableToDartListMultipartFileNullable` and `fromDartListMultipartFileNullableToGraphQLListUploadNullable` * `file: [Upload!]!` => `fromGraphQLListUploadToDartListMultipartFile` and `fromDartListMultipartFileToGraphQLListUpload` ```yaml targets: $default: builders: artemis: options: scalar_mapping: - custom_parser_import: 'package:graphbrainz_example/coercers.dart' graphql_type: Date dart_type: DateTime ``` If your custom scalar needs to import Dart libraries, you can provide it in the config as well: ```yaml targets: $default: builders: artemis: options: scalar_mapping: - custom_parser_import: 'package:graphbrainz_example/coercers.dart' graphql_type: BigDecimal dart_type: name: Decimal imports: - 'package:decimal/decimal.dart' ``` Each `ScalarMap` is configured this way: | Option | Default value | Description | | - | - | - | | `graphql_type` | | The GraphQL custom scalar name on schema. | | `dart_type` | | The Dart type this custom scalar should be converted from/to. | | `custom_parser_import` | `null` | Import path to the file implementing coercer functions for custom scalars. See [Custom scalars](#custom-scalars). | See [examples](./example) for more information and configuration options. ## **Articles and videos** 1. [Ultimate toolchain to work with GraphQL in Flutter](https://medium.com/@v.ditsyak/ultimate-toolchain-to-work-with-graphql-in-flutter-13aef79c6484) 2. [Awesome GraphQL](https://github.com/chentsulin/awesome-graphql) ## **ArtemisClient** If you have `generate_helpers`, Artemis will create a subclass of `GraphQLQuery` for you, this class can be used in conjunction with `ArtemisClient`. ```dart final client = ArtemisClient('/graphql'); final gitHubReposQuery = MyGitHubReposQuery(); final response = await client.execute(gitHubReposQuery); ``` `ArtemisClient` adds type-awareness around `Link` from [`package:gql/link`](https://pub.dev/packages/gql). You can create `ArtemisClient` from any `Link` using `ArtemisClient.fromLink`. Check the [examples](./example) to see how to use it in details.
================================================ FILE: analysis_options.yaml ================================================ # https://github.com/dart-lang/lints#migrating-from-packagepedantic include: package:lints/recommended.yaml analyzer: exclude: - example/**/*.dart language: strict-casts: true linter: rules: overridden_fields: false ================================================ FILE: build.yaml ================================================ builders: artemis: import: 'package:artemis/builder.dart' builder_factories: ['graphQLQueryBuilder'] build_extensions: {'$lib$': ['.graphql.dart']} auto_apply: dependents build_to: source applies_builders: ['json_serializable'] runs_before: ['json_serializable'] ================================================ FILE: example/.gitignore ================================================ /* !/pokemon !/graphbrainz !/github !/hasura ================================================ FILE: example/README.md ================================================ # **Examples** This folder contains some examples on how to use artemis. ## [**pokemon**](./pokemon) A simple example, showing [Pokémon GraphQL](https://graphql-pokemon.now.sh/) schema generation. ## [**graphqbrainz**](./graphbrainz) A more complex example, for [graphbrainz](https://graphbrainz.herokuapp.com) (a MusicBrainz GraphQL server). Featuring union types, interfaces and custom scalars. ## [**github**](./github) Even simpler example, for [GitHub GraphQL API](https://graphbrainz.herokuapp.com). I didn't commit the schema because it's too big (~3MB), so provide your own if you're running the example: https://github.com/octokit/graphql-schema ## [**hasura**](./hasura) This example uses a simple [Hasura](https://hasura.io/) server (with tables schema defined [in this file](./hasura/hasura.sql)), as an example of how to use Artemis with subscriptions. ================================================ FILE: example/github/.gitignore ================================================ github.schema.json github.schema.graphql ================================================ FILE: example/github/build.yaml ================================================ targets: $default: sources: - lib/** - github.schema.graphql builders: artemis: options: scalar_mapping: - graphql_type: GitObjectID dart_type: String - graphql_type: URI dart_type: String - graphql_type: GitRefname dart_type: String - graphql_type: DateTime dart_type: DateTime schema_mapping: - schema: github.schema.graphql queries_glob: lib/graphql/search_repositories.graphql output: lib/graphql/search_repositories.dart ================================================ FILE: example/github/lib/graphql/search_repositories.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND export 'search_repositories.graphql.dart'; ================================================ FILE: example/github/lib/graphql/search_repositories.graphql ================================================ query search_repositories($query: String!) { search(first: 10, type: REPOSITORY, query: $query) { nodes { __typename ... on Repository { name } } } } ================================================ FILE: example/github/lib/graphql/search_repositories.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'search_repositories.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository extends SearchRepositories$Query$SearchResultItemConnection$SearchResultItem with EquatableMixin { SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository(); factory SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository.fromJson( Map json) => _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$RepositoryFromJson( json); late String name; @override List get props => [name]; @override Map toJson() => _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$RepositoryToJson( this); } @JsonSerializable(explicitToJson: true) class SearchRepositories$Query$SearchResultItemConnection$SearchResultItem extends JsonSerializable with EquatableMixin { SearchRepositories$Query$SearchResultItemConnection$SearchResultItem(); factory SearchRepositories$Query$SearchResultItemConnection$SearchResultItem.fromJson( Map json) { switch (json['__typename'].toString()) { case r'Repository': return SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository .fromJson(json); default: } return _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItemFromJson( json); } @JsonKey(name: '__typename') String? $$typename; @override List get props => [$$typename]; @override Map toJson() { switch ($$typename) { case r'Repository': return (this as SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository) .toJson(); default: } return _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItemToJson( this); } } @JsonSerializable(explicitToJson: true) class SearchRepositories$Query$SearchResultItemConnection extends JsonSerializable with EquatableMixin { SearchRepositories$Query$SearchResultItemConnection(); factory SearchRepositories$Query$SearchResultItemConnection.fromJson( Map json) => _$SearchRepositories$Query$SearchResultItemConnectionFromJson(json); List? nodes; @override List get props => [nodes]; @override Map toJson() => _$SearchRepositories$Query$SearchResultItemConnectionToJson(this); } @JsonSerializable(explicitToJson: true) class SearchRepositories$Query extends JsonSerializable with EquatableMixin { SearchRepositories$Query(); factory SearchRepositories$Query.fromJson(Map json) => _$SearchRepositories$QueryFromJson(json); late SearchRepositories$Query$SearchResultItemConnection search; @override List get props => [search]; @override Map toJson() => _$SearchRepositories$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class SearchRepositoriesArguments extends JsonSerializable with EquatableMixin { SearchRepositoriesArguments({required this.query}); @override factory SearchRepositoriesArguments.fromJson(Map json) => _$SearchRepositoriesArgumentsFromJson(json); late String query; @override List get props => [query]; @override Map toJson() => _$SearchRepositoriesArgumentsToJson(this); } final SEARCH_REPOSITORIES_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'search_repositories'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'query')), type: NamedTypeNode(name: NameNode(value: 'String'), isNonNull: true), defaultValue: DefaultValueNode(value: null), directives: []) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'search'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'first'), value: IntValueNode(value: '10')), ArgumentNode( name: NameNode(value: 'type'), value: EnumValueNode(name: NameNode(value: 'REPOSITORY'))), ArgumentNode( name: NameNode(value: 'query'), value: VariableNode(name: NameNode(value: 'query'))) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'nodes'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: '__typename'), alias: null, arguments: [], directives: [], selectionSet: null), InlineFragmentNode( typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Repository'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ])) ])) ])) ]); class SearchRepositoriesQuery extends GraphQLQuery { SearchRepositoriesQuery({required this.variables}); @override final DocumentNode document = SEARCH_REPOSITORIES_QUERY_DOCUMENT; @override final String operationName = 'search_repositories'; @override final SearchRepositoriesArguments variables; @override List get props => [document, operationName, variables]; @override SearchRepositories$Query parse(Map json) => SearchRepositories$Query.fromJson(json); } ================================================ FILE: example/github/lib/graphql/search_repositories.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'search_repositories.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$RepositoryFromJson( Map json) => SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository() ..$$typename = json['__typename'] as String? ..name = json['name'] as String; Map _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$RepositoryToJson( SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository instance) => { '__typename': instance.$$typename, 'name': instance.name, }; SearchRepositories$Query$SearchResultItemConnection$SearchResultItem _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItemFromJson( Map json) => SearchRepositories$Query$SearchResultItemConnection$SearchResultItem() ..$$typename = json['__typename'] as String?; Map _$SearchRepositories$Query$SearchResultItemConnection$SearchResultItemToJson( SearchRepositories$Query$SearchResultItemConnection$SearchResultItem instance) => { '__typename': instance.$$typename, }; SearchRepositories$Query$SearchResultItemConnection _$SearchRepositories$Query$SearchResultItemConnectionFromJson( Map json) => SearchRepositories$Query$SearchResultItemConnection() ..nodes = (json['nodes'] as List?) ?.map((e) => e == null ? null : SearchRepositories$Query$SearchResultItemConnection$SearchResultItem .fromJson(e as Map)) .toList(); Map _$SearchRepositories$Query$SearchResultItemConnectionToJson( SearchRepositories$Query$SearchResultItemConnection instance) => { 'nodes': instance.nodes?.map((e) => e?.toJson()).toList(), }; SearchRepositories$Query _$SearchRepositories$QueryFromJson( Map json) => SearchRepositories$Query() ..search = SearchRepositories$Query$SearchResultItemConnection.fromJson( json['search'] as Map); Map _$SearchRepositories$QueryToJson( SearchRepositories$Query instance) => { 'search': instance.search.toJson(), }; SearchRepositoriesArguments _$SearchRepositoriesArgumentsFromJson( Map json) => SearchRepositoriesArguments( query: json['query'] as String, ); Map _$SearchRepositoriesArgumentsToJson( SearchRepositoriesArguments instance) => { 'query': instance.query, }; ================================================ FILE: example/github/lib/main.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:artemis/artemis.dart'; import 'package:http/http.dart' as http; import 'graphql/search_repositories.dart'; class AuthenticatedClient extends http.BaseClient { final http.Client _inner = http.Client(); Future send(http.BaseRequest request) { request.headers['Authorization'] = 'Bearer ${Platform.environment['GITHUB_TOKEN']}'; return _inner.send(request); } } Future main() async { final client = ArtemisClient( 'https://api.github.com/graphql', httpClient: AuthenticatedClient(), ); final query = SearchRepositoriesQuery( variables: SearchRepositoriesArguments(query: 'flutter'), ); final response = await client.execute(query); (response.data?.search.nodes ?? []) .whereType< SearchRepositories$Query$SearchResultItemConnection$SearchResultItem$Repository>() .map((r) => r.name) .forEach(print); } ================================================ FILE: example/github/pubspec.yaml ================================================ name: github_example version: 0.0.1 environment: sdk: ">=2.12.0 <3.0.0" dependencies: http: dev_dependencies: test: build_runner: json_serializable: lints: ^1.0.1 artemis: path: ../../. ================================================ FILE: example/graphbrainz/build.yaml ================================================ targets: $default: sources: - lib/** builders: artemis: options: schema_mapping: - schema: lib/graphbrainz.schema.graphql queries_glob: lib/queries/ed_sheeran.query.graphql output: lib/queries/ed_sheeran.query.dart custom_parser_import: 'package:graphbrainz_example/coercers.dart' scalar_mapping: - graphql_type: Date dart_type: DateTime use_custom_parser: true - graphql_type: Time dart_type: DateTime use_custom_parser: true - graphql_type: DiscID dart_type: String - graphql_type: MBID dart_type: String - graphql_type: ASIN dart_type: String - graphql_type: IPI dart_type: String - graphql_type: ISNI dart_type: String - graphql_type: ISRC dart_type: String - graphql_type: URLString dart_type: String - graphql_type: Degrees dart_type: double - graphql_type: Locale dart_type: String ================================================ FILE: example/graphbrainz/lib/coercers.dart ================================================ import 'package:intl/intl.dart'; final dateFormatter = DateFormat('yyyy-MM-dd'); final timeFormatter = DateFormat('HH:mm:ss'); DateTime fromGraphQLDateToDartDateTime(String date) => DateTime.parse(date); String fromDartDateTimeToGraphQLDate(DateTime date) => dateFormatter.format(date); DateTime fromGraphQLTimeToDartDateTime(String time) => DateTime.parse('1970-01-01T${time}Z'); String fromDartDateTimeToGraphQLTime(DateTime date) => timeFormatter.format(date); ================================================ FILE: example/graphbrainz/lib/graphbrainz.schema.graphql ================================================ """ [Aliases](https://musicbrainz.org/doc/Aliases) are variant names that are mostly used as search help: if a search matches an entity’s alias, the entity will be given as a result – even if the actual name wouldn’t be. """ type Alias { """The aliased name of the entity.""" name: String """ The string to use for the purpose of ordering by name (for example, by moving articles like ‘the’ to the end or a person’s last name to the front). """ sortName: String """ The locale (language and/or country) in which the alias is used. """ locale: Locale """ Whether this is the main alias for the entity in the specified locale (this could mean the most recent or the most common). """ primary: Boolean """ The type or purpose of the alias – whether it is a variant, search hint, etc. """ type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID } """ [Areas](https://musicbrainz.org/doc/Area) are geographic regions or settlements (countries, cities, or the like). """ type Area implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """ The string to use for the purpose of ordering by name (for example, by moving articles like ‘the’ to the end or a person’s last name to the front). """ sortName: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """ [ISO 3166 codes](https://en.wikipedia.org/wiki/ISO_3166) are the codes assigned by ISO to countries and subdivisions. """ isoCodes( """ Specify the particular ISO standard codes to retrieve. Available ISO standards are 3166-1, 3166-2, and 3166-3. """ standard: String = "3166-1" ): [String] """ The type of area (country, city, etc. – see the [possible values](https://musicbrainz.org/doc/Area)). """ type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """A list of artists linked to this entity.""" artists(after: String, first: Int): ArtistConnection """A list of events linked to this entity.""" events(after: String, first: Int): EventConnection """A list of labels linked to this entity.""" labels(after: String, first: Int): LabelConnection """A list of places linked to this entity.""" places(after: String, first: Int): PlaceConnection """A list of releases linked to this entity.""" releases( """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ Chart data available for this area on [Last.fm](https://www.last.fm/), if the area represents a country with an [ISO 3166 code](https://en.wikipedia.org/wiki/ISO_3166). This field is provided by the Last.fm extension. """ lastFM: LastFMCountry } """A connection to a list of items.""" type AreaConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [AreaEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Area] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type AreaEdge { """The item at the end of the edge""" node: Area """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ An [artist](https://musicbrainz.org/doc/Artist) is generally a musician, group of musicians, or other music professional (like a producer or engineer). Occasionally, it can also be a non-musical person (like a photographer, an illustrator, or a poet whose writings are set to music), or even a fictional character. """ type Artist implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """ The string to use for the purpose of ordering by name (for example, by moving articles like ‘the’ to the end or a person’s last name to the front). """ sortName: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """ The country with which an artist is primarily identified. It is often, but not always, its birth/formation country. """ country: String """ The area with which an artist is primarily identified. It is often, but not always, its birth/formation country. """ area: Area """ The area in which an artist began their career (or where they were born, if the artist is a person). """ beginArea: Area """ The area in which an artist ended their career (or where they died, if the artist is a person). """ endArea: Area """ The begin and end dates of the entity’s existence. Its exact meaning depends on the type of entity. """ lifeSpan: LifeSpan """ Whether a person or character identifies as male, female, or neither. Groups do not have genders. """ gender: String """ The MBID associated with the value of the `gender` field. """ genderID: MBID """Whether an artist is a person, a group, or something else.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """ List of [Interested Parties Information](https://musicbrainz.org/doc/IPI) (IPI) codes for the artist. """ ipis: [IPI] """ List of [International Standard Name Identifier](https://musicbrainz.org/doc/ISNI) (ISNI) codes for the artist. """ isnis: [ISNI] """A list of recordings linked to this entity.""" recordings(after: String, first: Int): RecordingConnection """A list of releases linked to this entity.""" releases( """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """A list of release groups linked to this entity.""" releaseGroups( """Filter by one or more release group types.""" type: [ReleaseGroupType] after: String first: Int ): ReleaseGroupConnection """A list of works linked to this entity.""" works(after: String, first: Int): WorkConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """The rating users have given to this entity.""" rating: Rating """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ Images of the artist from [fanart.tv](https://fanart.tv/). This field is provided by the fanart.tv extension. """ fanArt: FanArtArtist """ Artist images found at MediaWiki URLs in the artist’s URL relationships. Defaults to URL relationships with the type “image”. This field is provided by the MediaWiki extension. """ mediaWikiImages( """ The type of URL relationship that will be selected to find images. See the possible [Artist-URL relationship types](https://musicbrainz.org/relationships/artist-url). """ type: String = "image" ): [MediaWikiImage]! """ Data about the artist from [TheAudioDB](http://www.theaudiodb.com/), a good source of biographical information and images. This field is provided by TheAudioDB extension. """ theAudioDB: TheAudioDBArtist """Information about the artist on Discogs.""" discogs: DiscogsArtist """ Data about the artist from [Last.fm](https://www.last.fm/), a good source for measuring popularity via listener and play counts. This field is provided by the Last.fm extension. """ lastFM: LastFMArtist """The artist’s entry on Spotify.""" spotify: SpotifyArtist } """A connection to a list of items.""" type ArtistConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [ArtistEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Artist] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """ [Artist credits](https://musicbrainz.org/doc/Artist_Credits) indicate who is the main credited artist (or artists) for releases, release groups, tracks, and recordings, and how they are credited. They consist of artists, with (optionally) their names as credited in the specific release, track, etc., and join phrases between them. """ type ArtistCredit { """ The entity representing the artist referenced in the credits. """ artist: Artist """ The name of the artist as credited in the specific release, track, etc. """ name: String """ Join phrases might include words and/or punctuation to separate artist names as they appear on the release, track, etc. """ joinPhrase: String } """An edge in a connection.""" type ArtistEdge { """The item at the end of the edge""" node: Artist """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ An [Amazon Standard Identification Number](https://musicbrainz.org/doc/ASIN) (ASIN) is a 10-character alphanumeric unique identifier assigned by Amazon.com and its partners for product identification within the Amazon organization. """ scalar ASIN """ A query for all MusicBrainz entities directly linked to another entity. """ type BrowseQuery { """Browse area entities linked to the given arguments.""" areas( """The MBID of a collection in which the entity is found.""" collection: MBID after: String first: Int ): AreaConnection """Browse artist entities linked to the given arguments.""" artists( """The MBID of an area to which the entity is linked.""" area: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """The MBID of a recording to which the entity is linked.""" recording: MBID """The MBID of a release to which the entity is linked.""" release: MBID """The MBID of a release group to which the entity is linked.""" releaseGroup: MBID """The MBID of a work to which the entity is linked.""" work: MBID after: String first: Int ): ArtistConnection """Browse collection entities linked to the given arguments.""" collections( """The MBID of an area to which the entity is linked.""" area: MBID """The MBID of an artist to which the entity is linked.""" artist: MBID """The username of the editor who created the collection.""" editor: String """The MBID of an event to which the entity is linked.""" event: MBID """The MBID of a label to which the entity is linked.""" label: MBID """The MBID of a place to which the entity is linked.""" place: MBID """The MBID of a recording to which the entity is linked.""" recording: MBID """The MBID of a release to which the entity is linked.""" release: MBID """The MBID of a release group to which the entity is linked.""" releaseGroup: MBID """The MBID of a work to which the entity is linked.""" work: MBID after: String first: Int ): CollectionConnection """Browse event entities linked to the given arguments.""" events( """The MBID of an area to which the entity is linked.""" area: MBID """The MBID of an artist to which the entity is linked.""" artist: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """The MBID of a place to which the entity is linked.""" place: MBID after: String first: Int ): EventConnection """Browse label entities linked to the given arguments.""" labels( """The MBID of an area to which the entity is linked.""" area: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """The MBID of a release to which the entity is linked.""" release: MBID after: String first: Int ): LabelConnection """Browse place entities linked to the given arguments.""" places( """The MBID of an area to which the entity is linked.""" area: MBID """The MBID of a collection in which the entity is found.""" collection: MBID after: String first: Int ): PlaceConnection """Browse recording entities linked to the given arguments.""" recordings( """The MBID of an artist to which the entity is linked.""" artist: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """ The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC) (ISRC) of the recording. """ isrc: ISRC """The MBID of a release to which the entity is linked.""" release: MBID after: String first: Int ): RecordingConnection """Browse release entities linked to the given arguments.""" releases( """The MBID of an area to which the entity is linked.""" area: MBID """The MBID of an artist to which the entity is linked.""" artist: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """ A [disc ID](https://musicbrainz.org/doc/Disc_ID) associated with the release. """ discID: DiscID """The MBID of a label to which the entity is linked.""" label: MBID """The MBID of a recording to which the entity is linked.""" recording: MBID """The MBID of a release group to which the entity is linked.""" releaseGroup: MBID """The MBID of a track that is included in the release.""" track: MBID """ The MBID of an artist that appears on a track in the release, but is not included in the credits for the release itself. """ trackArtist: MBID """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """Browse release group entities linked to the given arguments.""" releaseGroups( """The MBID of an artist to which the entity is linked.""" artist: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """The MBID of a release to which the entity is linked.""" release: MBID """Filter by one or more release group types.""" type: [ReleaseGroupType] after: String first: Int ): ReleaseGroupConnection """Browse work entities linked to the given arguments.""" works( """The MBID of an artist to which the entity is linked.""" artist: MBID """The MBID of a collection in which the entity is found.""" collection: MBID """ The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC) (ISWC) of the work. """ iswc: ISWC after: String first: Int ): WorkConnection } """ [Collections](https://musicbrainz.org/doc/Collections) are lists of entities that users can create. """ type Collection implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """The username of the editor who created the collection.""" editor: String! """The type of entity listed in the collection.""" entityType: String! """The type of collection.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """The list of areas found in this collection.""" areas(after: String, first: Int): AreaConnection """The list of artists found in this collection.""" artists(after: String, first: Int): ArtistConnection """The list of events found in this collection.""" events(after: String, first: Int): EventConnection """The list of instruments found in this collection.""" instruments(after: String, first: Int): InstrumentConnection """The list of labels found in this collection.""" labels(after: String, first: Int): LabelConnection """The list of places found in this collection.""" places(after: String, first: Int): PlaceConnection """The list of recordings found in this collection.""" recordings(after: String, first: Int): RecordingConnection """The list of releases found in this collection.""" releases( """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """The list of release groups found in this collection.""" releaseGroups( """Filter by one or more release group types.""" type: [ReleaseGroupType] after: String first: Int ): ReleaseGroupConnection """The list of series found in this collection.""" series(after: String, first: Int): SeriesConnection """The list of works found in this collection.""" works(after: String, first: Int): WorkConnection } """A connection to a list of items.""" type CollectionConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [CollectionEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Collection] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type CollectionEdge { """The item at the end of the edge""" node: Collection """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """Geographic coordinates described with latitude and longitude.""" type Coordinates { """The north–south position of a point on the Earth’s surface.""" latitude: Degrees """The east–west position of a point on the Earth’s surface.""" longitude: Degrees } """ An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). """ type CoverArtArchiveImage { """The Internet Archive’s internal file ID for the image.""" fileID: String! """The URL at which the image can be found.""" image: URLString! """A set of thumbnails for the image.""" thumbnails: CoverArtArchiveImageThumbnails! """Whether this image depicts the “main front” of the release.""" front: Boolean! """Whether this image depicts the “main back” of the release.""" back: Boolean! """ A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types) describing what part(s) of the release the image includes. """ types: [String]! """The MusicBrainz edit ID.""" edit: Int """Whether the image was approved by the MusicBrainz edit system.""" approved: Boolean """A free-text comment left for the image.""" comment: String } """ The image sizes that may be requested at the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). """ enum CoverArtArchiveImageSize { """A maximum dimension of 250px.""" SMALL """A maximum dimension of 500px.""" LARGE """The image’s original dimensions, with no maximum.""" FULL } """ URLs for thumbnails of different sizes for a particular piece of cover art. """ type CoverArtArchiveImageThumbnails { """ The URL of a small version of the cover art, where the maximum dimension is 250px. """ small: URLString """ The URL of a large version of the cover art, where the maximum dimension is 500px. """ large: URLString } """ An object containing a list of the cover art images for a release obtained from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive), as well as a summary of what artwork is available. """ type CoverArtArchiveRelease { """ The URL of an image depicting the album cover or “main front” of the release, i.e. the front of the packaging of the audio recording (or in the case of a digital release, the image associated with it in a digital media store). In the MusicBrainz schema, this field is a Boolean value indicating the presence of a front image, whereas here the value is the URL for the image itself if one exists. You can check for null if you just want to determine the presence of an image. """ front( """ The size of the image to retrieve. By default, the returned image will have its full original dimensions, but certain thumbnail sizes may be retrieved as well. """ size: CoverArtArchiveImageSize = FULL ): URLString """ The URL of an image depicting the “main back” of the release, i.e. the back of the packaging of the audio recording. In the MusicBrainz schema, this field is a Boolean value indicating the presence of a back image, whereas here the value is the URL for the image itself. You can check for null if you just want to determine the presence of an image. """ back( """ The size of the image to retrieve. By default, the returned image will have its full original dimensions, but certain thumbnail sizes may be retrieved as well. """ size: CoverArtArchiveImageSize = FULL ): URLString """ A list of images depicting the different sides and surfaces of a release’s media and packaging. """ images: [CoverArtArchiveImage]! """Whether there is artwork present for this release.""" artwork: Boolean! """The number of artwork images present for this release.""" count: Int! """The particular release shown in the returned cover art.""" release: Release } """Year, month (optional), and day (optional) in YYYY-MM-DD format.""" scalar Date """Decimal degrees, used for latitude and longitude.""" scalar Degrees """ Information about the physical CD and releases associated with a particular [disc ID](https://musicbrainz.org/doc/Disc_ID). """ type Disc implements Node { """The ID of an object""" id: ID! """The [disc ID](https://musicbrainz.org/doc/Disc_ID) of this disc.""" discID: DiscID! """The number of offsets (tracks) on the disc.""" offsetCount: Int! """The sector offset of each track on the disc.""" offsets: [Int] """The sector offset of the lead-out (the end of the disc).""" sectors: Int! """The list of releases linked to this disc ID.""" releases(after: String, first: Int): ReleaseConnection } """ [Disc ID](https://musicbrainz.org/doc/Disc_ID) is the code number which MusicBrainz uses to link a physical CD to a [release](https://musicbrainz.org/doc/Release) listing. A release may have any number of disc IDs, and a disc ID may be linked to multiple releases. This is because disc ID calculation involves a hash of the frame offsets of the CD tracks. Different pressing of a CD often have slightly different frame offsets, and hence different disc IDs. Conversely, two different CDs may happen to have exactly the same set of frame offsets and hence the same disc ID. """ scalar DiscID """An artist on Discogs.""" type DiscogsArtist { """The ID of the artist on Discogs.""" artistID: ID! """The name of the artist on Discogs.""" name: String! """Commonly found variations of the artist’s name.""" nameVariations: [String!]! """ The artist’s real name, if the artist is a person who uses a stage name. """ realName: String """ A list of Discogs artists that represent the same artist under a different alias. """ aliases: [DiscogsArtist!]! """The URL of the artist’s page on Discogs.""" url: URLString! """Links to the artist’s official pages on different web properties.""" urls: [URLString!]! """A biography or description of the artist.""" profile: String """A list of images picturing the artist.""" images: [DiscogsImage!]! """A list of members, if the artist is a group.""" members: [DiscogsArtistMember!]! """ A description of the quality and completeness of this artist’s data in the Discogs database. """ dataQuality: String } """A credited artist on a release, track, etc.""" type DiscogsArtistCredit { """The official or common name of the credited artist.""" name: String """ The artist name as credited on this particular work (the Artist Name Variation, or ANV, in Discogs terms). """ nameVariation: String """ Join phrases might include words and/or punctuation to separate artist names as they appear on the release, track, etc. """ joinPhrase: String """A list of roles the artist had on the work in question.""" roles: [String!]! """ A list of tracks or track ranges (e.g. “A1 to A4”) on which the artist is credited. """ tracks: [String!]! """The artist’s entry on Discogs.""" artist: DiscogsArtist } """A single artist who is a member of a group on Discogs.""" type DiscogsArtistMember { """Whether or not the member is still active in the group.""" active: Boolean """The name of the member.""" name: String! """The member’s artist information on Discogs.""" artist: DiscogsArtist } """Community statistics regarding an item on Discogs.""" type DiscogsCommunity { """The acceptance status.""" status: String """Information about how Discogs users have rated the item.""" rating: DiscogsRating """The number of Discogs users who have the item in their collection.""" haveCount: Int """The number of Discogs users who want the item.""" wantCount: Int """The Discogs users who have contributed to the item’s data.""" contributors: [DiscogsUser!]! """The Discogs user who submitted the item.""" submitter: DiscogsUser } """A single image from Discogs.""" type DiscogsImage { """The URL of the image file.""" url: URLString! """The image type, primary or secondary.""" type: DiscogsImageType! """The image width in pixels.""" width: Int! """The image height in pixels.""" height: Int! """The URL for a 150x150 thumbnail of the image.""" thumbnail: URLString } """The type of image.""" enum DiscogsImageType { """The primary image representing the item.""" PRIMARY """A secondary image representing the item.""" SECONDARY } """A label on Discogs.""" type DiscogsLabel { """The ID of the label on Discogs.""" labelID: ID! """The name of the label.""" name: String! """The URL of the label on Discogs.""" url: URLString! """A description of the history of the label.""" profile: String """Information on how to contact a representative of the label.""" contactInfo: String """The parent label, if this label is a subsidiary.""" parentLabel: DiscogsLabel """A list of labels that are subsidiaries of this label.""" subLabels: [DiscogsLabel!]! """A list of images associated with the label.""" images: [DiscogsImage!]! """ A description of the quality and completeness of this label’s data in the Discogs database. """ dataQuality: String } """ Master releases group different versions of the same release (for example, releases in different formats, issued in different countries, re-releases, etc.). The equivalent of a MusicBrainz release group. """ type DiscogsMaster { """The ID of the master on Discogs.""" masterID: ID! """The title of the master.""" title: String! """The URL of the master on Discogs.""" url: URLString! """The artists credited on the master.""" artistCredits: [DiscogsArtistCredit!]! """The primary musical genres of the master (e.g. “Electronic”).""" genres: [String!]! """The primary musical styles of the master (e.g. “Techno”, “Minimal”).""" styles: [String!]! """The number of listings the master currently has on the marketplace.""" forSaleCount: Int """The lowest price for the master currently found on the marketplace.""" lowestPrice( """ The three-letter currency code for which to retrieve the price. Discogs supports USD, GBP, EUR, CAD, AUD, JPY, CHF, MXN, BRL, NZD, SEK, and ZAR. # [NOT YET WORKING] """ currency: String ): Float """The year the master was released (most likely its “main” release).""" year: Int """The main release from the master.""" mainRelease: DiscogsRelease """Images of the master.""" images: [DiscogsImage!]! """Music videos from the master.""" videos: [DiscogsVideo!]! """ A description of the quality and completeness of this master’s data in the Discogs database. """ dataQuality: String } """An aggregated rating on Discogs.""" type DiscogsRating { """The number of users who have contributed to the rating.""" voteCount: Int! """The average rating as determined by users.""" value: Float } """A release on Discogs.""" type DiscogsRelease { """The ID of the release on Discogs.""" releaseID: ID! """The title of the release.""" title: String! """The URL of the release on Discogs.""" url: URLString! """The artists credited on the release.""" artistCredits: [DiscogsArtistCredit!]! """ An additional list of artists who contributed to the release, but are not named in the release’s artists. """ extraArtistCredits: [DiscogsArtistCredit!]! """The primary musical genres of the release (e.g. “Electronic”).""" genres: [String!]! """The primary musical styles of the release (e.g. “Techno”, “Minimal”).""" styles: [String!]! """The number of listings the release currently has on the marketplace.""" forSaleCount: Int """The lowest price for the release currently found on the marketplace.""" lowestPrice( """ The three-letter currency code for which to retrieve the price. Discogs supports USD, GBP, EUR, CAD, AUD, JPY, CHF, MXN, BRL, NZD, SEK, and ZAR. # [NOT YET WORKING] """ currency: String ): Float """The year the release was issued.""" year: Int """Notes about the release.""" notes: String """The country in which the release was issued.""" country: String """The master release on Discogs.""" master: DiscogsMaster """The primary thumbnail image for the release.""" thumbnail: URLString """Images of the release.""" images: [DiscogsImage!]! """Music videos from the release.""" videos: [DiscogsVideo!]! """ Information about the Discogs community’s contributions to the release’s data. """ community: DiscogsCommunity """ A description of the quality and completeness of this release’s data in the Discogs database. """ dataQuality: String } """A connection to a list of Discogs releases.""" type DiscogsReleaseConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [DiscogsReleaseEdge!]! """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [DiscogsRelease!]! """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a Discogs release connection.""" type DiscogsReleaseEdge { """The release at the end of the edge.""" node: DiscogsRelease! } """A user on Discogs.""" type DiscogsUser { """The user’s username on Discogs.""" username: String! } """A single video linked from Discogs.""" type DiscogsVideo { """The URL of the video.""" url: URLString! """The title of the video.""" title: String """The video description.""" description: String """The duration of the video in milliseconds.""" duration: Duration """Whether the video is embeddable.""" embed: Boolean } """A length of time, in milliseconds.""" scalar Duration """An entity in the MusicBrainz schema.""" interface Entity { """The MBID of the entity.""" mbid: MBID! } """ An [event](https://musicbrainz.org/doc/Event) refers to an organised event which people can attend, and is relevant to MusicBrainz. Generally this means live performances, like concerts and festivals. """ type Event implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """ The begin and end dates of the entity’s existence. Its exact meaning depends on the type of entity. """ lifeSpan: LifeSpan """The start time of the event.""" time: Time """Whether or not the event took place.""" cancelled: Boolean """ A list of songs performed, optionally including links to artists and works. See the [setlist documentation](https://musicbrainz.org/doc/Event/Setlist) for syntax and examples. """ setlist: String """What kind of event the event is, e.g. concert, festival, etc.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """The rating users have given to this entity.""" rating: Rating """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection } """A connection to a list of items.""" type EventConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [EventEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Event] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type EventEdge { """The item at the end of the edge""" node: Event """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ An object containing lists of the different types of release group images from [fanart.tv](https://fanart.tv/). """ type FanArtAlbum { """ A list of 1000x1000 JPG images of the cover artwork of the release group. """ albumCovers: [FanArtImage] """ A list of 1000x1000 PNG images of the physical disc media for the release group, with transparent backgrounds. """ discImages: [FanArtDiscImage] } """ An object containing lists of the different types of artist images from [fanart.tv](https://fanart.tv/). """ type FanArtArtist { """ A list of 1920x1080 JPG images picturing the artist, suitable for use as backgrounds. """ backgrounds: [FanArtImage] """ A list of 1000x185 JPG images containing the artist and their logo or name. """ banners: [FanArtImage] """ A list of 400x155 PNG images containing the artist’s logo or name, with transparent backgrounds. """ logos: [FanArtImage] """ A list of 800x310 PNG images containing the artist’s logo or name, with transparent backgrounds. """ logosHD: [FanArtImage] """ A list of 1000x1000 JPG thumbnail images picturing the artist (usually containing every member of a band). """ thumbnails: [FanArtImage] } """A disc image from [fanart.tv](https://fanart.tv/).""" type FanArtDiscImage { """The ID of the image on fanart.tv.""" imageID: ID """The URL of the image.""" url( """The size of the image to retrieve.""" size: FanArtImageSize = FULL ): URLString """The number of likes the image has received by fanart.tv users.""" likeCount: Int """The disc number.""" discNumber: Int """The width and height of the (square) disc image.""" size: Int } """A single image from [fanart.tv](https://fanart.tv/).""" type FanArtImage { """The ID of the image on fanart.tv.""" imageID: ID """The URL of the image.""" url( """The size of the image to retrieve.""" size: FanArtImageSize = FULL ): URLString """The number of likes the image has received by fanart.tv users.""" likeCount: Int } """ The image sizes that may be requested at [fanart.tv](https://fanart.tv/). """ enum FanArtImageSize { """The image’s full original dimensions.""" FULL """A maximum dimension of 200px.""" PREVIEW } """ An object containing lists of the different types of label images from [fanart.tv](https://fanart.tv/). """ type FanArtLabel { """ A list of 400x270 PNG images containing the label’s logo. There will usually be a black version, a color version, and a white version, all with transparent backgrounds. """ logos: [FanArtLabelImage] } """A music label image from [fanart.tv](https://fanart.tv/).""" type FanArtLabelImage { """The ID of the image on fanart.tv.""" imageID: ID """The URL of the image.""" url( """The size of the image to retrieve.""" size: FanArtImageSize = FULL ): URLString """The number of likes the image has received by fanart.tv users.""" likeCount: Int """The type of color content in the image (usually “white” or “colour”).""" color: String } """ [Instruments](https://musicbrainz.org/doc/Instrument) are devices created or adapted to make musical sounds. Instruments are primarily used in relationships between two other entities. """ type Instrument implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """ A brief description of the main characteristics of the instrument. """ description: String """ The type categorises the instrument by the way the sound is created, similar to the [Hornbostel-Sachs](https://en.wikipedia.org/wiki/Hornbostel%E2%80%93Sachs) classification. """ type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ Instrument images found at MediaWiki URLs in the instrument’s URL relationships. Defaults to URL relationships with the type “image”. This field is provided by the MediaWiki extension. """ mediaWikiImages( """ The type of URL relationship that will be selected to find images. See the possible [Instrument-URL relationship types](https://musicbrainz.org/relationships/instrument-url). """ type: String = "image" ): [MediaWikiImage]! } """A connection to a list of items.""" type InstrumentConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [InstrumentEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Instrument] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type InstrumentEdge { """The item at the end of the edge""" node: Instrument """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ An [Interested Parties Information](https://musicbrainz.org/doc/IPI) (IPI) code is an identifying number assigned by the CISAC database for musical rights management. """ scalar IPI """ The [International Standard Name Identifier](https://musicbrainz.org/doc/ISNI) (ISNI) is an ISO standard for uniquely identifying the public identities of contributors to media content. """ scalar ISNI """ The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC) (ISRC) is an identification system for audio and music video recordings. It is standarized by the [IFPI](http://www.ifpi.org/) in ISO 3901:2001 and used by IFPI members to assign a unique identifier to every distinct sound recording they release. An ISRC identifies a particular [sound recording](https://musicbrainz.org/doc/Recording), not the song itself. Therefore, different recordings, edits, remixes and remasters of the same song will each be assigned their own ISRC. However, note that same recording should carry the same ISRC in all countries/territories. Songs are identified by analogous [International Standard Musical Work Codes](https://musicbrainz.org/doc/ISWC) (ISWCs). """ scalar ISRC """ The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC) (ISWC) is an ISO standard similar to ISBNs for identifying musical works / compositions. """ scalar ISWC """ [Labels](https://musicbrainz.org/doc/Label) represent mostly (but not only) imprints. To a lesser extent, a label entity may be created to represent a record company. """ type Label implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """ The string to use for the purpose of ordering by name (for example, by moving articles like ‘the’ to the end or a person’s last name to the front). """ sortName: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """The country of origin for the label.""" country: String """The area in which the label is based.""" area: Area """ The begin and end dates of the entity’s existence. Its exact meaning depends on the type of entity. """ lifeSpan: LifeSpan """ The [“LC” code](https://musicbrainz.org/doc/Label/Label_Code) of the label. """ labelCode: Int """ List of [Interested Parties Information](https://musicbrainz.org/doc/IPI) codes for the label. """ ipis: [IPI] """ A type describing the main activity of the label, e.g. imprint, production, distributor, rights society, etc. """ type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """A list of releases linked to this entity.""" releases( """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """The rating users have given to this entity.""" rating: Rating """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ Images of the label from [fanart.tv](https://fanart.tv/). This field is provided by the fanart.tv extension. """ fanArt: FanArtLabel """ Label images found at MediaWiki URLs in the label’s URL relationships. Defaults to URL relationships with the type “logo”. This field is provided by the MediaWiki extension. """ mediaWikiImages( """ The type of URL relationship that will be selected to find images. See the possible [Label-URL relationship types](https://musicbrainz.org/relationships/label-url). """ type: String = "logo" ): [MediaWikiImage]! """Information about the label on Discogs.""" discogs: DiscogsLabel } """A connection to a list of items.""" type LabelConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [LabelEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Label] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type LabelEdge { """The item at the end of the edge""" node: Label """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ An album on [Last.fm](https://www.last.fm/) corresponding with a MusicBrainz Release. """ type LastFMAlbum { """The MBID of the corresponding MusicBrainz release.""" mbid: MBID """The title of the album according to [Last.fm](https://www.last.fm/).""" title: String """The URL for the album on [Last.fm](https://www.last.fm/).""" url: URLString! """An image of the cover artwork of the release.""" image( """The size of the image to retrieve.""" size: LastFMImageSize ): URLString """The number of listeners recorded for the album.""" listenerCount: Float """The number of plays recorded for the album.""" playCount: Float """ Historical information written about the album, often available in several languages. """ description( """ The two-letter code for the language in which to retrieve the description. """ lang: String ): LastFMWikiContent """ The artist who released the album. This returns the Last.fm artist info, not the MusicBrainz artist. """ artist: LastFMArtist """A list of tags applied to the artist by users, ordered by popularity.""" topTags( """The maximum number of tags to retrieve.""" first: Int = 25 """The cursor of the edge after which more tags will be retrieved.""" after: String ): LastFMTagConnection } """A connection to a list of items.""" type LastFMAlbumConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [LastFMAlbumEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [LastFMAlbum] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type LastFMAlbumEdge { """The item at the end of the edge.""" node: LastFMAlbum """A cursor for use in pagination.""" cursor: String! } """An artist on [Last.fm](https://www.last.fm/).""" type LastFMArtist { """The MBID of the corresponding MusicBrainz artist.""" mbid: MBID """The name of the artist according to [Last.fm](https://www.last.fm/).""" name: String """The URL for the artist on [Last.fm](https://www.last.fm/).""" url: URLString! """An image of the artist.""" image( """The size of the image to retrieve.""" size: LastFMImageSize ): URLString """The number of listeners recorded for the artist.""" listenerCount: Float """The number of plays recorded for the artist.""" playCount: Float """A list of similar artists.""" similarArtists( """The maximum number of artists to retrieve.""" first: Int = 25 """The cursor of the edge after which more artists will be retrieved.""" after: String ): LastFMArtistConnection """A list of the artist’s most popular albums.""" topAlbums( """The maximum number of albums to retrieve.""" first: Int = 25 """The cursor of the edge after which more albums will be retrieved.""" after: String ): LastFMAlbumConnection """A list of tags applied to the artist by users, ordered by popularity.""" topTags( """The maximum number of tags to retrieve.""" first: Int = 25 """The cursor of the edge after which more tags will be retrieved.""" after: String ): LastFMTagConnection """A list of the artist’s most popular tracks.""" topTracks( """The maximum number of tracks to retrieve.""" first: Int = 25 """The cursor of the edge after which more tracks will be retrieved.""" after: String ): LastFMTrackConnection """A biography of the artist, often available in several languages.""" biography( """ The two-letter code for the language in which to retrieve the biography. """ lang: String ): LastFMWikiContent } """A connection to a list of items.""" type LastFMArtistConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [LastFMArtistEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [LastFMArtist] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type LastFMArtistEdge { """The item at the end of the edge.""" node: LastFMArtist """A cursor for use in pagination.""" cursor: String! """ The artist similarity score (0–1) as determined by [Last.fm](https://www.last.fm/), if this connection is with another artist. """ matchScore: Float } """A query for chart data on [Last.fm](https://www.last.fm/).""" type LastFMChartQuery { """ The most popular artists, ordered by popularity. If a country code is given, retrieve the most popular artists in that country. """ topArtists( """ A two-letter [ISO 3166 country code](https://en.wikipedia.org/wiki/ISO_3166). """ country: String """The maximum number of artists to retrieve.""" first: Int = 25 """The cursor of the edge after which more artists will be retrieved.""" after: String ): LastFMArtistConnection """The most popular tags, ordered by popularity.""" topTags( """The maximum number of tags to retrieve.""" first: Int = 25 """The cursor of the edge after which more tags will be retrieved.""" after: String ): LastFMTagConnection """ The most popular tracks, ordered by popularity. If a country code is given, retrieve the most popular tracks in that country. """ topTracks( """ A two-letter [ISO 3166 country code](https://en.wikipedia.org/wiki/ISO_3166). """ country: String """The maximum number of tracks to retrieve.""" first: Int = 25 """The cursor of the edge after which more tracks will be retrieved.""" after: String ): LastFMTrackConnection } """ A country with chart data available on [Last.fm](https://www.last.fm/). """ type LastFMCountry { """The top artists in this country, ordered by popularity.""" topArtists( """The maximum number of artists to retrieve.""" first: Int = 25 """The cursor of the edge after which more artists will be retrieved.""" after: String ): LastFMArtistConnection """The top tracks in this country, ordered by popularity.""" topTracks( """The maximum number of tracks to retrieve.""" first: Int = 25 """The cursor of the edge after which more tracks will be retrieved.""" after: String ): LastFMTrackConnection } """ The image sizes that may be requested at [Last.fm](https://www.last.fm/). """ enum LastFMImageSize { """A maximum dimension of 34px.""" SMALL """A maximum dimension of 64px.""" MEDIUM """A maximum dimension of 174px.""" LARGE """A maximum dimension of 300px.""" EXTRALARGE """A maximum dimension of 300px.""" MEGA } """ The different types of [Last.fm](https://www.last.fm/) queries that can be made that are not connected to any particular MusicBrainz entity. """ type LastFMQuery { """A query for chart data.""" chart: LastFMChartQuery! } """A tag added by users to an entity on [Last.fm](https://www.last.fm/).""" type LastFMTag { """The tag name.""" name: String! """The URL for the tag on [Last.fm](https://www.last.fm/).""" url: URLString! } """A connection to a list of items.""" type LastFMTagConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [LastFMTagEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [LastFMTag] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type LastFMTagEdge { """The item at the end of the edge.""" node: LastFMTag """A cursor for use in pagination.""" cursor: String! """The number of times the tag has been applied to the item in question.""" tagCount: Int } """ A track on [Last.fm](https://www.last.fm/) corresponding with a MusicBrainz Recording. """ type LastFMTrack { """The MBID of the corresponding MusicBrainz recording.""" mbid: MBID """The title of the track according to [Last.fm](https://www.last.fm/).""" title: String """The URL for the track on [Last.fm](https://www.last.fm/).""" url: URLString! """The length of the track.""" duration: Duration """The number of listeners recorded for the track.""" listenerCount: Float """The number of plays recorded for the track.""" playCount: Float """ Historical information written about the track, often available in several languages. """ description( """ The two-letter code for the language in which to retrieve the description. """ lang: String ): LastFMWikiContent """ The artist who released the track. This returns the Last.fm artist info, not the MusicBrainz artist. """ artist: LastFMArtist """ The album on which the track appears. This returns the Last.fm album info, not the MusicBrainz release. """ album: LastFMAlbum """A list of similar tracks.""" similarTracks( """The maximum number of tracks to retrieve.""" first: Int = 25 """The cursor of the edge after which more tracks will be retrieved.""" after: String ): LastFMTrackConnection """A list of tags applied to the track by users, ordered by popularity.""" topTags( """The maximum number of tags to retrieve.""" first: Int = 25 """The cursor of the edge after which more tags will be retrieved.""" after: String ): LastFMTagConnection } """A connection to a list of items.""" type LastFMTrackConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [LastFMTrackEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [LastFMTrack] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type LastFMTrackEdge { """The item at the end of the edge.""" node: LastFMTrack """A cursor for use in pagination.""" cursor: String! """ The track similarity score (0–1) as determined by [Last.fm](https://www.last.fm/), if this connection is with another track. """ matchScore: Float } """ Biographical or background information written about an entity on [Last.fm](https://www.last.fm/). """ type LastFMWikiContent { """A summary of the wiki content, which may contain HTML.""" summaryHTML: String """The full wiki content, which may contain HTML.""" contentHTML: String """ The date the content was published. The data is reformatted from the Last.fm API’s original format into the Date scalar format. """ publishDate: Date """ The time the content was published. The data is reformatted from the Last.fm API’s original format into the Time scalar format. The API offers no indication as to which time zone the time is in. """ publishTime: Time """The URL at which the content was published.""" url: URLString } """ Fields indicating the begin and end date of an entity’s lifetime, including whether it has ended (even if the date is unknown). """ type LifeSpan { """The start date of the entity’s life span.""" begin: Date """The end date of the entity’s life span.""" end: Date """Whether or not the entity’s life span has ended.""" ended: Boolean } """Language code, optionally with country and encoding.""" scalar Locale """A lookup of an individual MusicBrainz entity by its MBID.""" type LookupQuery { """Look up a specific area by its MBID.""" area( """The MBID of the entity.""" mbid: MBID! ): Area """Look up a specific artist by its MBID.""" artist( """The MBID of the entity.""" mbid: MBID! ): Artist """Look up a specific collection by its MBID.""" collection( """The MBID of the entity.""" mbid: MBID! ): Collection """Look up a specific physical disc by its disc ID.""" disc( """ The [disc ID](https://musicbrainz.org/doc/Disc_ID) of the disc. """ discID: DiscID! ): Disc """Look up a specific event by its MBID.""" event( """The MBID of the entity.""" mbid: MBID! ): Event """Look up a specific instrument by its MBID.""" instrument( """The MBID of the entity.""" mbid: MBID! ): Instrument """Look up a specific label by its MBID.""" label( """The MBID of the entity.""" mbid: MBID! ): Label """Look up a specific place by its MBID.""" place( """The MBID of the entity.""" mbid: MBID! ): Place """Look up a specific recording by its MBID.""" recording( """The MBID of the entity.""" mbid: MBID! ): Recording """Look up a specific release by its MBID.""" release( """The MBID of the entity.""" mbid: MBID! ): Release """Look up a specific release group by its MBID.""" releaseGroup( """The MBID of the entity.""" mbid: MBID! ): ReleaseGroup """Look up a specific series by its MBID.""" series( """The MBID of the entity.""" mbid: MBID! ): Series """Look up a specific URL by its MBID.""" url( """The MBID of the entity.""" mbid: MBID """The web address of the URL entity to look up.""" resource: URLString ): URL """Look up a specific work by its MBID.""" work( """The MBID of the entity.""" mbid: MBID! ): Work } """ The MBID scalar represents MusicBrainz identifiers, which are 36-character UUIDs. """ scalar MBID """ An object describing various properties of an image stored on a MediaWiki server. The information comes the [MediaWiki imageinfo API](https://www.mediawiki.org/wiki/API:Imageinfo). """ type MediaWikiImage { """The URL of the actual image file.""" url: URLString! """The URL of the wiki page describing the image.""" descriptionURL: URLString """The user who uploaded the file.""" user: String """The size of the file in bytes.""" size: Int """The pixel width of the image.""" width: Int """The pixel height of the image.""" height: Int """The canonical title of the file.""" canonicalTitle: String """The image title, brief description, or file name.""" objectName: String """A description of the image, potentially containing HTML.""" descriptionHTML: String """ The original date of creation of the image. May be a description rather than a parseable timestamp, and may contain HTML. """ originalDateTimeHTML: String """A list of the categories of the image.""" categories: [String]! """The name of the image author, potentially containing HTML.""" artistHTML: String """The source of the image, potentially containing HTML.""" creditHTML: String """A short human-readable license name.""" licenseShortName: String """A web address where the license is described.""" licenseURL: URLString """The full list of values in the `extmetadata` field.""" metadata: [MediaWikiImageMetadata]! } """An entry in the `extmetadata` field of a MediaWiki image file.""" type MediaWikiImageMetadata { """The name of the metadata field.""" name: String! """ The value of the metadata field. All values will be converted to strings. """ value: String """The source of the value.""" source: String } """ A medium is the actual physical medium the audio content is stored upon. This means that each CD in a multi-disc release will be entered as separate mediums within the release, and that both sides of a vinyl record or cassette will exist on one medium. Mediums have a format (e.g. CD, DVD, vinyl, cassette) and can optionally also have a title. """ type Medium { """The title of this particular medium.""" title: String """ The [format](https://musicbrainz.org/doc/Release/Format) of the medium (e.g. CD, DVD, vinyl, cassette). """ format: String """ The MBID associated with the value of the `format` field. """ formatID: MBID """ The order of this medium in the release (for example, in a multi-disc release). """ position: Int """The number of audio tracks on this medium.""" trackCount: Int """A list of physical discs and their disc IDs for this medium.""" discs: [Disc] """The list of tracks on the given media.""" tracks: [Track] } """An object with an ID""" interface Node { """The id of the object.""" id: ID! } """Information about pagination in a connection.""" type PageInfo { """When paginating forwards, are there more items?""" hasNextPage: Boolean! """When paginating backwards, are there more items?""" hasPreviousPage: Boolean! """When paginating backwards, the cursor to continue.""" startCursor: String """When paginating forwards, the cursor to continue.""" endCursor: String } """ A [place](https://musicbrainz.org/doc/Place) is a venue, studio, or other place where music is performed, recorded, engineered, etc. """ type Place implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """ The address describes the location of the place using the standard addressing format for the country it is located in. """ address: String """ The area entity representing the area, such as the city, in which the place is located. """ area: Area """The geographic coordinates of the place.""" coordinates: Coordinates """ The begin and end dates of the entity’s existence. Its exact meaning depends on the type of entity. """ lifeSpan: LifeSpan """ The type categorises the place based on its primary function. """ type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """A list of events linked to this entity.""" events(after: String, first: Int): EventConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ Place images found at MediaWiki URLs in the place’s URL relationships. Defaults to URL relationships with the type “image”. This field is provided by the MediaWiki extension. """ mediaWikiImages( """ The type of URL relationship that will be selected to find images. See the possible [Place-URL relationship types](https://musicbrainz.org/relationships/place-url). """ type: String = "image" ): [MediaWikiImage]! } """A connection to a list of items.""" type PlaceConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [PlaceEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Place] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type PlaceEdge { """The item at the end of the edge""" node: Place """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ The query root, from which multiple types of MusicBrainz requests can be made. """ type Query { """Perform a lookup of a MusicBrainz entity by its MBID.""" lookup: LookupQuery """Browse all MusicBrainz entities directly linked to another entity.""" browse: BrowseQuery """Search for MusicBrainz entities using Lucene query syntax.""" search: SearchQuery """Fetches an object given its ID""" node( """The ID of an object""" id: ID! ): Node """ A query for data on [Last.fm](https://www.last.fm/) that is not connected to any particular MusicBrainz entity. This field is provided by the Last.fm extension. """ lastFM: LastFMQuery spotify: SpotifyQuery! } """ [Ratings](https://musicbrainz.org/doc/Rating_System) allow users to rate MusicBrainz entities. User may assign a value between 1 and 5; these values are then aggregated by the server to compute an average community rating for the entity. """ type Rating { """The number of votes that have contributed to the rating.""" voteCount: Int! """The average rating value based on the aggregated votes.""" value: Float } """ A [recording](https://musicbrainz.org/doc/Recording) is an entity in MusicBrainz which can be linked to tracks on releases. Each track must always be associated with a single recording, but a recording can be linked to any number of tracks. A recording represents distinct audio that has been used to produce at least one released track through copying or mastering. A recording itself is never produced solely through copying or mastering. Generally, the audio represented by a recording corresponds to the audio at a stage in the production process before any final mastering but after any editing or mixing. """ type Recording implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official title of the entity.""" title: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """The main credited artist(s).""" artistCredit: [ArtistCredit] @deprecated(reason: "The `artistCredit` field has been renamed to\n`artistCredits`, since it is a list of credits and is referred to in the\nplural form throughout the MusicBrainz documentation. This field is deprecated\nand will be removed in a major release in the future. Use the equivalent\n`artistCredits` field.") """The main credited artist(s).""" artistCredits: [ArtistCredit] """ A list of [International Standard Recording Codes](https://musicbrainz.org/doc/ISRC) (ISRCs) for this recording. """ isrcs: [ISRC] """ An approximation to the length of the recording, calculated from the lengths of the tracks using it. """ length: Duration """Whether this is a video recording.""" video: Boolean """A list of artists linked to this entity.""" artists(after: String, first: Int): ArtistConnection """A list of releases linked to this entity.""" releases( """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """The rating users have given to this entity.""" rating: Rating """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ Data about the recording from [TheAudioDB](http://www.theaudiodb.com/). This field is provided by TheAudioDB extension. """ theAudioDB: TheAudioDBTrack """ Data about the recording from [Last.fm](https://www.last.fm/), a good source for measuring popularity via listener and play counts. This field is provided by the Last.fm extension. """ lastFM: LastFMTrack """The recording’s entry on Spotify.""" spotify( """ The strategies to use to match the recording with a Spotify track, in preferential order. """ strategy: [SpotifyMatchStrategy!] = [URL, EXTERNALID] ): SpotifyTrack } """A connection to a list of items.""" type RecordingConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [RecordingEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Recording] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type RecordingEdge { """The item at the end of the edge""" node: Recording """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ [Relationships](https://musicbrainz.org/doc/Relationships) are a way to represent all the different ways in which entities are connected to each other and to URLs outside MusicBrainz. """ type Relationship { """The target entity.""" target: Entity! """The direction of the relationship.""" direction: String! """The type of entity on the receiving end of the relationship.""" targetType: String! """ How the source entity was actually credited, if different from its main (performance) name. """ sourceCredit: String """ How the target entity was actually credited, if different from its main (performance) name. """ targetCredit: String """The date on which the relationship became applicable.""" begin: Date """The date on which the relationship became no longer applicable.""" end: Date """Whether the relationship still applies.""" ended: Boolean """ Attributes which modify the relationship. There is a [list of all attributes](https://musicbrainz.org/relationship-attributes), but the attributes which are available, and how they should be used, depends on the relationship type. """ attributes: [String] """The type of relationship.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID } """A connection to a list of items.""" type RelationshipConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [RelationshipEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Relationship] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type RelationshipEdge { """The item at the end of the edge""" node: Relationship """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """Lists of entity relationships for each entity type.""" type Relationships { """A list of relationships between these two entity types.""" areas( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" artists( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" events( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" instruments( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" labels( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" places( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" recordings( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" releases( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" releaseGroups( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" series( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" urls( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection """A list of relationships between these two entity types.""" works( """Filter by the relationship direction.""" direction: String """Filter by the relationship type.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID after: String first: Int before: String last: Int ): RelationshipConnection } """ A [release](https://musicbrainz.org/doc/Release) represents the unique release (i.e. issuing) of a product on a specific date with specific release information such as the country, label, barcode, packaging, etc. If you walk into a store and purchase an album or single, they’re each represented in MusicBrainz as one release. """ type Release implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official title of the entity.""" title: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """The main credited artist(s).""" artistCredit: [ArtistCredit] @deprecated(reason: "The `artistCredit` field has been renamed to\n`artistCredits`, since it is a list of credits and is referred to in the\nplural form throughout the MusicBrainz documentation. This field is deprecated\nand will be removed in a major release in the future. Use the equivalent\n`artistCredits` field.") """The main credited artist(s).""" artistCredits: [ArtistCredit] """The release events for this release.""" releaseEvents: [ReleaseEvent] """ The [release date](https://musicbrainz.org/doc/Release/Date) is the date in which a release was made available through some sort of distribution mechanism. """ date: Date """The country in which the release was issued.""" country: String """ The [Amazon Standard Identification Number](https://musicbrainz.org/doc/ASIN) of the release. """ asin: ASIN """ The [barcode](https://en.wikipedia.org/wiki/Barcode), if the release has one. The most common types found on releases are 12-digit [UPCs](https://en.wikipedia.org/wiki/Universal_Product_Code) and 13-digit [EANs](https://en.wikipedia.org/wiki/International_Article_Number). """ barcode: String """The status describes how “official” a release is.""" status: ReleaseStatus """ The MBID associated with the value of the `status` field. """ statusID: MBID """ The physical packaging that accompanies the release. See the [list of packaging](https://musicbrainz.org/doc/Release/Packaging) for more information. """ packaging: String """ The MBID associated with the value of the `packaging` field. """ packagingID: MBID """ Data quality indicates how good the data for a release is. It is not a mark of how good or bad the music itself is – for that, use [ratings](https://musicbrainz.org/doc/Rating_System). """ quality: String """The media on which the release was distributed.""" media: [Medium] """A list of artists linked to this entity.""" artists(after: String, first: Int): ArtistConnection """A list of labels linked to this entity.""" labels(after: String, first: Int): LabelConnection """A list of recordings linked to this entity.""" recordings(after: String, first: Int): RecordingConnection """A list of release groups linked to this entity.""" releaseGroups( """Filter by one or more release group types.""" type: [ReleaseGroupType] after: String first: Int ): ReleaseGroupConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ An object containing a list and summary of the cover art images that are present for this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). This field is provided by the Cover Art Archive extension. """ coverArtArchive: CoverArtArchiveRelease """Information about the release on Discogs.""" discogs: DiscogsRelease """ Data about the release from [Last.fm](https://www.last.fm/), a good source for measuring popularity via listener and play counts. This field is provided by the Last.fm extension. """ lastFM: LastFMAlbum """The release’s entry on Spotify.""" spotify( """ The strategies to use to match the release with a Spotify album, in preferential order. """ strategy: [SpotifyMatchStrategy!] = [URL, EXTERNALID] ): SpotifyAlbum } """A connection to a list of items.""" type ReleaseConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [ReleaseEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Release] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type ReleaseEdge { """The item at the end of the edge""" node: Release """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ The date on which a release was issued in a country/region with a particular label, catalog number, barcode, and format. """ type ReleaseEvent { area: Area date: Date } """ A [release group](https://musicbrainz.org/doc/Release_Group) is used to group several different releases into a single logical entity. Every release belongs to one, and only one release group. Both release groups and releases are “albums” in a general sense, but with an important difference: a release is something you can buy as media such as a CD or a vinyl record, while a release group embraces the overall concept of an album – it doesn’t matter how many CDs or editions/versions it had. """ type ReleaseGroup implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official title of the entity.""" title: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """The main credited artist(s).""" artistCredit: [ArtistCredit] @deprecated(reason: "The `artistCredit` field has been renamed to\n`artistCredits`, since it is a list of credits and is referred to in the\nplural form throughout the MusicBrainz documentation. This field is deprecated\nand will be removed in a major release in the future. Use the equivalent\n`artistCredits` field.") """The main credited artist(s).""" artistCredits: [ArtistCredit] """The date of the earliest release in the group.""" firstReleaseDate: Date """ The [type](https://musicbrainz.org/doc/Release_Group/Type) of a release group describes what kind of releases the release group represents, e.g. album, single, soundtrack, compilation, etc. A release group can have a “main” type and an unspecified number of additional types. """ primaryType: ReleaseGroupType """ The MBID associated with the value of the `primaryType` field. """ primaryTypeID: MBID """ Additional [types](https://musicbrainz.org/doc/Release_Group/Type) that apply to this release group. """ secondaryTypes: [ReleaseGroupType] """ The MBIDs associated with the values of the `secondaryTypes` field. """ secondaryTypeIDs: [MBID] """A list of artists linked to this entity.""" artists(after: String, first: Int): ArtistConnection """A list of releases linked to this entity.""" releases( """Filter by one or more release group types.""" type: [ReleaseGroupType] """Filter by one or more release statuses.""" status: [ReleaseStatus] after: String first: Int ): ReleaseConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """The rating users have given to this entity.""" rating: Rating """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection """ The cover art for a release in the release group, obtained from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). A release in the release group will be chosen as representative of the release group. This field is provided by the Cover Art Archive extension. """ coverArtArchive: CoverArtArchiveRelease """ Images of the release group from [fanart.tv](https://fanart.tv/). This field is provided by the fanart.tv extension. """ fanArt: FanArtAlbum """ Data about the release group from [TheAudioDB](http://www.theaudiodb.com/), a good source of descriptive information, reviews, and images. This field is provided by TheAudioDB extension. """ theAudioDB: TheAudioDBAlbum """Information about the release group on Discogs.""" discogs: DiscogsMaster } """A connection to a list of items.""" type ReleaseGroupConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [ReleaseGroupEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [ReleaseGroup] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type ReleaseGroupEdge { """The item at the end of the edge""" node: ReleaseGroup """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ A type used to describe release groups, e.g. album, single, EP, etc. """ enum ReleaseGroupType { """ An album, perhaps better defined as a “Long Play” (LP) release, generally consists of previously unreleased material (unless this type is combined with secondary types which change that, such as “Compilation”). This includes album re-issues, with or without bonus tracks. """ ALBUM """ A single typically has one main song and possibly a handful of additional tracks or remixes of the main track. A single is usually named after its main song. """ SINGLE """ An EP is a so-called “Extended Play” release and often contains the letters EP in the title. Generally an EP will be shorter than a full length release (an LP or “Long Play”) and the tracks are usually exclusive to the EP, in other words the tracks don’t come from a previously issued release. EP is fairly difficult to define; usually it should only be assumed that a release is an EP if the artist defines it as such. """ EP """Any release that does not fit any of the other categories.""" OTHER """ An episodic release that was originally broadcast via radio, television, or the Internet, including podcasts. """ BROADCAST """ A compilation is a collection of previously released tracks by one or more artists. """ COMPILATION """ A soundtrack is the musical score to a movie, TV series, stage show, computer game, etc. """ SOUNDTRACK """A non-music spoken word release.""" SPOKENWORD """ An interview release contains an interview, generally with an artist. """ INTERVIEW """An audiobook is a book read by a narrator without music.""" AUDIOBOOK """A release that was recorded live.""" LIVE """ A release that was (re)mixed from previously released material. """ REMIX """ A DJ-mix is a sequence of several recordings played one after the other, each one modified so that they blend together into a continuous flow of music. A DJ mix release requires that the recordings be modified in some manner, and the DJ who does this modification is usually (although not always) credited in a fairly prominent way. """ DJMIX """ Promotional in nature (but not necessarily free), mixtapes and street albums are often released by artists to promote new artists, or upcoming studio albums by prominent artists. They are also sometimes used to keep fans’ attention between studio releases and are most common in rap & hip hop genres. They are often not sanctioned by the artist’s label, may lack proper sample or song clearances and vary widely in production and recording quality. While mixtapes are generally DJ-mixed, they are distinct from commercial DJ mixes (which are usually deemed compilations) and are defined by having a significant proportion of new material, including original production or original vocals over top of other artists’ instrumentals. They are distinct from demos in that they are designed for release directly to the public and fans, not to labels. """ MIXTAPE """ A release that was recorded for limited circulation or reference use rather than for general public release. """ DEMO """A non-album track (special case).""" NAT } """ A type used to describe the status of releases, e.g. official, bootleg, etc. """ enum ReleaseStatus { """ Any release officially sanctioned by the artist and/or their record company. (Most releases will fit into this category.) """ OFFICIAL """ A giveaway release or a release intended to promote an upcoming official release, e.g. prerelease albums or releases included with a magazine. """ PROMOTION """ An unofficial/underground release that was not sanctioned by the artist and/or the record company. """ BOOTLEG """ A pseudo-release is a duplicate release for translation/transliteration purposes. """ PSEUDORELEASE } """A search for MusicBrainz entities using Lucene query syntax.""" type SearchQuery { """Search for area entities matching the given query.""" areas( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): AreaConnection """Search for artist entities matching the given query.""" artists( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): ArtistConnection """Search for event entities matching the given query.""" events( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): EventConnection """Search for instrument entities matching the given query.""" instruments( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): InstrumentConnection """Search for label entities matching the given query.""" labels( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): LabelConnection """Search for place entities matching the given query.""" places( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): PlaceConnection """Search for recording entities matching the given query.""" recordings( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): RecordingConnection """Search for release entities matching the given query.""" releases( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): ReleaseConnection """Search for release group entities matching the given query.""" releaseGroups( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): ReleaseGroupConnection """Search for series entities matching the given query.""" series( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): SeriesConnection """Search for work entities matching the given query.""" works( """ The query terms, in Lucene search syntax. See [examples and search fields](https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search). """ query: String! after: String first: Int ): WorkConnection } """ A [series](https://musicbrainz.org/doc/Series) is a sequence of separate release groups, releases, recordings, works or events with a common theme. """ type Series implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official name of the entity.""" name: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ The type primarily describes what type of entity the series contains. """ type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection } """A connection to a list of items.""" type SeriesConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [SeriesEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Series] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type SeriesEdge { """The item at the end of the edge""" node: Series """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """An album from Spotify.""" type SpotifyAlbum { """ The [Spotify ID](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the album. """ albumID: ID! """ The [Spotify URI](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the album. """ uri: String! """A link to the Web API endpoint providing full details of the album.""" href: URLString! """ The name of the album. In case of an album takedown, the value may be empty. """ title: String """The type of the album, e.g. “Album”, “Single”, “Compilation”.""" albumType: ReleaseGroupType! """The artists of the album.""" artists: [SpotifyArtist!]! """ The markets in which the album is available: [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes. Note that an album is considered available in a market when at least 1 of its tracks is available in that market. """ availableMarkets: [String!]! """The copyright statements of the album.""" copyrights: [SpotifyCopyright!]! """Known external IDs for the album.""" externalIDs: [SpotifyExternalID!]! """Known external URLs for this album.""" externalURLs: [SpotifyExternalURL!]! """ A list of the genres used to classify the album. For example: “Prog Rock”, “Post-Grunge”. (If not yet classified, the array is empty.) """ genres: [String!]! """The cover art for the album in various sizes, widest first.""" images: [SpotifyImage!]! """The label for the album.""" label: String """ The popularity of the album. The value will be between 0 and 100, with 100 being the most popular. The popularity is calculated from the popularity of the album’s individual tracks. """ popularity: Int! """ The date the album was first released, for example “1981-12-15”. Depending on the precision, the month or day might be missing. """ releaseDate: Date } """An artist from Spotify.""" type SpotifyArtist { """ The [Spotify ID](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the artist. """ artistID: ID! """ The [Spotify URI](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the artist. """ uri: String! """A link to the Web API endpoint providing full details of the artist.""" href: URLString! """The name of the artist.""" name: String! """Known external URLs for this artist.""" externalURLs: [SpotifyExternalURL!]! """ A list of the genres the artist is associated with. For example: “Prog Rock”, “Post-Grunge”. (If not yet classified, the array is empty.) """ genres: [String!]! """ The popularity of the artist. The value will be between 0 and 100, with 100 being the most popular. The artist’s popularity is calculated from the popularity of all the artist’s tracks. """ popularity: Int! """Images of the artist in various sizes, widest first.""" images: [SpotifyImage!]! """Spotify catalog information about an artist’s top tracks by country.""" topTracks( """ An [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code. """ market: String! ): [SpotifyTrack!]! """ Spotify catalog information about artists similar to a given artist. Similarity is based on analysis of the Spotify community’s listening history. """ relatedArtists: [SpotifyArtist!]! } """The audio features of a track from Spotify.""" type SpotifyAudioFeatures { """ A confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 represents high confidence the track is acoustic. """ acousticness: Float! """ Danceability describes how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is least danceable and 1.0 is most danceable. """ danceability: Float! """The duration of the track in milliseconds.""" duration: Duration! """ Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity. Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy, while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, and general entropy. """ energy: Float! """ Predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as instrumental in this context. Rap or spoken word tracks are clearly “vocal”. The closer the instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the value approaches 1.0. """ instrumentalness: Float! """ The key the track is in. Integers map to pitches using standard [Pitch Class notation](https://en.wikipedia.org/wiki/Pitch_class), e.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on. See the `keyName` field if you’d prefer the note as a string. """ key: Int! """ The `key` translated from an integer to a name like “C”. (Only one name will be returned, so enharmonic notes like like C♯/D♭ will just return “C♯”.) """ keyName: String! """ Detects the presence of an audience in the recording. Higher liveness values represent an increased probability that the track was performed live. A value above 0.8 provides strong likelihood that the track is live. """ liveness: Float! """ The overall loudness of a track in decibels (dB). Loudness values are averaged across the entire track and are useful for comparing relative loudness of tracks. Loudness is the quality of a sound that is the primary psychological correlate of physical strength (amplitude). Values typical range between -60 and 0 db. """ loudness: Float! """ Mode indicates the modality (major or minor) of a track, the type of scale from which its melodic content is derived. Major is represented by 1 and minor is 0. """ mode: SpotifyTrackMode! """ Speechiness detects the presence of spoken words in a track. The more exclusively speech-like the recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values above 0.66 describe tracks that are probably made entirely of spoken words. Values between 0.33 and 0.66 describe tracks that may contain both music and speech, either in sections or layered, including such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like tracks. """ speechiness: Float! """ The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a given piece and derives directly from the average beat duration. """ tempo: Float! """ An estimated overall time signature of a track. The time signature (meter) is a notational convention to specify how many beats are in each bar (or measure). """ timeSignature: Float! """ A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry). """ valence: Float! } """A copyright statement for an album from Spotify.""" type SpotifyCopyright { """The copyright text.""" text: String! """ Whether the copyright is for the work itself or the sound recording (performance). """ type: SpotifyCopyrightType! } """The type of copyright.""" enum SpotifyCopyrightType { """The copyright.""" COPYRIGHT """The sound recording (performance) copyright.""" PERFORMANCE } """A value for identifying an entity with some third party.""" type SpotifyExternalID { """The identifier type, for example “isrc”, “ean”, “upc”.""" type: String! """The identifier value.""" id: String! } """A URL for linking to an entity with some third party.""" type SpotifyExternalURL { """The type of the URL, for example “spotify”.""" type: String! """An external, public URL to the object.""" url: URLString! } """A single image from Spotify.""" type SpotifyImage { """The source URL of the image.""" url: URLString! """The image width in pixels, if known.""" width: Int """The image height in pixels, if known.""" height: Int } """Strategies for matching MusicBrainz entities to Spotify entities.""" enum SpotifyMatchStrategy { """ The entity will be matched by finding an explicit URL relationship that links to Spotify. """ URL """ The entity will be matched by searching for Spotify entities by some external ID that is known to both MusicBrainz and Spotify, like an ISRC or UPC barcode. Since this can result in multiple Spotify matches, the most popular will be preferred (if possible), or the first. """ EXTERNALID } type SpotifyQuery { """Track recommendations based on seed entities and various parameters.""" recommendations( """ A list of [Spotify IDs](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for seed artists. Up to 5 seed values may be provided in any combination of `seedArtists`, `seedTracks`, and `seedGenres`. """ seedArtists: [ID!] = [] """ A comma separated list of any genres in the set of [available genre seeds](https://developer.spotify.com/documentation/web-api/reference/browse/get-recommendations/#available-genre-seeds). Up to 5 seed values may be provided in any combination of `seedArtists`, `seedTracks`, and `seedGenres`. """ seedGenres: [ID!] = [] """ A list of [Spotify IDs](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for seed tracks. Up to 5 seed values may be provided in any combination of `seedArtists`, `seedTracks`, and `seedGenres`. """ seedTracks: [ID!] = [] """ The target size of the list of recommended tracks. For seeds with unusually small pools or when highly restrictive filtering is applied, it may be impossible to generate the requested number of recommended tracks. Debugging information for such cases is available in the response. Default: 20. Minimum: 1. Maximum: 100. """ limit: Int ): SpotifyRecommendations! } type SpotifyRecommendations { tracks: [SpotifyTrack!]! } """A track from Spotify.""" type SpotifyTrack { """ The [Spotify ID](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the track. """ trackID: ID! """ The [Spotify URI](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the track. """ uri: String! """A link to the Web API endpoint providing full details of the track.""" href: URLString! """The name of the track.""" title: String! """The audio features of the track.""" audioFeatures: SpotifyAudioFeatures """The album on which the track appears.""" album: SpotifyAlbum """The artists who performed the track.""" artists: [SpotifyArtist!]! """ A list of the countries in which the track can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. """ availableMarkets: [String!]! """ The disc number (usually `1` unless the album consists of more than one disc). """ discNumber: Int! """The track length in milliseconds.""" duration: Duration! """Whether or not the track has explicit lyrics, if known.""" explicit: Boolean """Known external IDs for the track.""" externalIDs: [SpotifyExternalID!]! """Known external URLs for the track.""" externalURLs: [SpotifyExternalURL!]! """ The popularity of the track. The value will be between 0 and 100, with 100 being the most popular. The popularity is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how recent those plays are. Generally speaking, songs that are being played a lot now will have a higher popularity than songs that were played a lot in the past. Duplicate tracks (e.g. the same track from a single and an album) are rated independently. Artist and album popularity is derived mathematically from track popularity. Note that the popularity value may lag actual popularity by a few days: the value is not updated in real time. """ popularity: Int! """A link to a 30 second preview (MP3 format) of the track, if available.""" previewURL: URLString """ The number of the track. If an album has several discs, the track number is the number on the specified disc. """ trackNumber: Int! """A MusicBrainz recording that corresponds to the track.""" musicBrainz( """ The strategies to use to match the track with a MusicBrainz recording, in preferential order. """ strategy: [SpotifyMatchStrategy!] = [URL, EXTERNALID] ): Recording } """The potential values for modality (major or minor) of a track.""" enum SpotifyTrackMode { """The major scale.""" MAJOR """The minor scale.""" MINOR } """ [Tags](https://musicbrainz.org/tags) are a way to mark entities with extra information – for example, the genres that apply to an artist, release, or recording. """ type Tag { """The tag label.""" name: String! """How many times this tag has been applied to the entity.""" count: Int } """A connection to a list of items.""" type TagConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [TagEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Tag] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type TagEdge { """The item at the end of the edge""" node: Tag """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } """ An album on [TheAudioDB](http://www.theaudiodb.com/) corresponding with a MusicBrainz Release Group. """ type TheAudioDBAlbum { """TheAudioDB ID of the album.""" albumID: ID """TheAudioDB ID of the artist who released the album.""" artistID: ID """A description of the album, often available in several languages.""" description( """ The two-letter code for the language in which to retrieve the biography. """ lang: String = "en" ): String """A review of the album.""" review: String """The worldwide sales figure.""" salesCount: Float """The album’s rating as determined by user votes, out of 10.""" score: Float """The number of users who voted to determine the album’s score.""" scoreVotes: Float """An image of the physical disc media for the album.""" discImage( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """An image of the spine of the album packaging.""" spineImage( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """An image of the front of the album packaging.""" frontImage( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """An image of the back of the album packaging.""" backImage( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """The primary musical genre of the album (e.g. “Alternative Rock”).""" genre: String """The primary musical mood of the album (e.g. “Sad”).""" mood: String """The primary musical style of the album (e.g. “Rock/Pop”).""" style: String """ A rough description of the primary musical speed of the album (e.g. “Medium”). """ speed: String """The primary musical theme of the album (e.g. “In Love”).""" theme: String } """An artist on [TheAudioDB](http://www.theaudiodb.com/).""" type TheAudioDBArtist { """TheAudioDB ID of the artist.""" artistID: ID """A biography of the artist, often available in several languages.""" biography( """ The two-letter code for the language in which to retrieve the biography. """ lang: String = "en" ): String """The number of members in the musical group, if applicable.""" memberCount: Int """ A 1000x185 JPG banner image containing the artist and their logo or name. """ banner( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """A list of 1280x720 or 1920x1080 JPG images depicting the artist.""" fanArt( """The size of the images to retrieve.""" size: TheAudioDBImageSize = FULL ): [URLString]! """ A 400x155 PNG image containing the artist’s logo or name, with a transparent background. """ logo( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """ A 1000x1000 JPG thumbnail image picturing the artist (usually containing every member of a band). """ thumbnail( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """The primary musical genre of the artist (e.g. “Alternative Rock”).""" genre: String """The primary musical mood of the artist (e.g. “Sad”).""" mood: String """The primary musical style of the artist (e.g. “Rock/Pop”).""" style: String } """ The image sizes that may be requested at [TheAudioDB](http://www.theaudiodb.com/). """ enum TheAudioDBImageSize { """The image’s full original dimensions.""" FULL """A maximum dimension of 200px.""" PREVIEW } """ Details of a music video associated with a track on [TheAudioDB](http://www.theaudiodb.com/). """ type TheAudioDBMusicVideo { """The URL where the music video can be found.""" url: URLString """The video production company of the music video.""" companyName: String """The director of the music video.""" directorName: String """A list of still images from the music video.""" screenshots( """The size of the images to retrieve.""" size: TheAudioDBImageSize = FULL ): [URLString]! """ The number of views the video has received at the given URL. This will rarely be up to date, so use cautiously. """ viewCount: Float """ The number of likes the video has received at the given URL. This will rarely be up to date, so use cautiously. """ likeCount: Float """ The number of dislikes the video has received at the given URL. This will rarely be up to date, so use cautiously. """ dislikeCount: Float """ The number of comments the video has received at the given URL. This will rarely be up to date, so use cautiously. """ commentCount: Float } """ A track on [TheAudioDB](http://www.theaudiodb.com/) corresponding with a MusicBrainz Recording. """ type TheAudioDBTrack { """TheAudioDB ID of the track.""" trackID: ID """TheAudioDB ID of the album on which the track appears.""" albumID: ID """TheAudioDB ID of the artist who released the track.""" artistID: ID """A description of the track.""" description( """ The two-letter code for the language in which to retrieve the description. """ lang: String = "en" ): String """A thumbnail image for the track.""" thumbnail( """The size of the image to retrieve.""" size: TheAudioDBImageSize = FULL ): URLString """The track’s rating as determined by user votes, out of 10.""" score: Float """The number of users who voted to determine the album’s score.""" scoreVotes: Float """The track number of the song on the album.""" trackNumber: Int """The official music video for the track.""" musicVideo: TheAudioDBMusicVideo """The primary musical genre of the track (e.g. “Alternative Rock”).""" genre: String """The primary musical mood of the track (e.g. “Sad”).""" mood: String """The primary musical style of the track (e.g. “Rock/Pop”).""" style: String """The primary musical theme of the track (e.g. “In Love”).""" theme: String } """A time of day, in 24-hour hh:mm notation.""" scalar Time """ A track is the way a recording is represented on a particular release (or, more exactly, on a particular medium). Every track has a title (see the guidelines for titles) and is credited to one or more artists. """ type Track implements Entity { """The MBID of the entity.""" mbid: MBID! """The official title of the entity.""" title: String """ The track’s position on the overall release (including all tracks from all discs). """ position: Int """ The track number, which may include information about the disc or side it appears on, e.g. “A1” or “B3”. """ number: String """The length of the track.""" length: Duration """The recording that appears on the track.""" recording: Recording } """ A [URL](https://musicbrainz.org/doc/URL) pointing to a resource external to MusicBrainz, i.e. an official homepage, a site where music can be acquired, an entry in another database, etc. """ type URL implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The actual URL string.""" resource: URLString! """Relationships between this entity and other entitites.""" relationships: Relationships } """A web address.""" scalar URLString """ A [work](https://musicbrainz.org/doc/Work) is a distinct intellectual or artistic creation, which can be expressed in the form of one or more audio recordings. """ type Work implements Node & Entity { """The ID of an object""" id: ID! """The MBID of the entity.""" mbid: MBID! """The official title of the entity.""" title: String """A comment used to help distinguish identically named entitites.""" disambiguation: String """ [Aliases](https://musicbrainz.org/doc/Aliases) are used to store alternate names or misspellings. """ aliases: [Alias] """ A list of [ISWCs](https://musicbrainz.org/doc/ISWC) assigned to the work by copyright collecting agencies. """ iswcs: [String] """The language in which the work was originally written.""" language: String """The type of work.""" type: String """ The MBID associated with the value of the `type` field. """ typeID: MBID """A list of artists linked to this entity.""" artists(after: String, first: Int): ArtistConnection """Relationships between this entity and other entitites.""" relationships: Relationships """A list of collections containing this entity.""" collections(after: String, first: Int): CollectionConnection """The rating users have given to this entity.""" rating: Rating """A list of tags linked to this entity.""" tags(after: String, first: Int): TagConnection } """A connection to a list of items.""" type WorkConnection { """Information to aid in pagination.""" pageInfo: PageInfo! """A list of edges.""" edges: [WorkEdge] """ A list of nodes in the connection (without going through the `edges` field). """ nodes: [Work] """ A count of the total number of items in this connection, ignoring pagination. """ totalCount: Int } """An edge in a connection.""" type WorkEdge { """The item at the end of the edge""" node: Work """A cursor for use in pagination""" cursor: String! """ The relevancy score (0–100) assigned by the search engine, if these results were found through a search. """ score: Int } ================================================ FILE: example/graphbrainz/lib/main.dart ================================================ import 'package:artemis/artemis.dart'; import 'queries/ed_sheeran.query.dart'; void main() async { final client = ArtemisClient( 'https://graphbrainz.herokuapp.com/', ); final query = EdSheeranQuery(); final query2 = EdSheeranQuery(); final response = await client.execute(query); client.dispose(); print('Equality works: ${query == query2}'); if (response.hasErrors) { return print('Error: ${response.errors!.map((e) => e.message).toList()}'); } print(response.data?.node?.$$typename); final edSheeran = response.data?.node as EdSheeran$Query$Node$Artist; print(edSheeran.name); print(edSheeran.lifeSpan?.begin); } ================================================ FILE: example/graphbrainz/lib/queries/ed_sheeran.query.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND export 'ed_sheeran.query.graphql.dart'; ================================================ FILE: example/graphbrainz/lib/queries/ed_sheeran.query.graphql ================================================ query ed_sheeran { node(id: "QXJ0aXN0OmI4YTdjNTFmLTM2MmMtNGRjYi1hMjU5LWJjNmUwMDk1ZjBhNg==") { __typename id ... on Artist { mbid name releases { nodes { id status } } lifeSpan { begin } spotify { href } } } } ================================================ FILE: example/graphbrainz/lib/queries/ed_sheeran.query.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'ed_sheeran.query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class EdSheeran$Query$Node$Artist$ReleaseConnection$Release extends JsonSerializable with EquatableMixin { EdSheeran$Query$Node$Artist$ReleaseConnection$Release(); factory EdSheeran$Query$Node$Artist$ReleaseConnection$Release.fromJson( Map json) => _$EdSheeran$Query$Node$Artist$ReleaseConnection$ReleaseFromJson(json); late String id; @JsonKey(unknownEnumValue: ReleaseStatus.artemisUnknown) ReleaseStatus? status; @override List get props => [id, status]; @override Map toJson() => _$EdSheeran$Query$Node$Artist$ReleaseConnection$ReleaseToJson(this); } @JsonSerializable(explicitToJson: true) class EdSheeran$Query$Node$Artist$ReleaseConnection extends JsonSerializable with EquatableMixin { EdSheeran$Query$Node$Artist$ReleaseConnection(); factory EdSheeran$Query$Node$Artist$ReleaseConnection.fromJson( Map json) => _$EdSheeran$Query$Node$Artist$ReleaseConnectionFromJson(json); List? nodes; @override List get props => [nodes]; @override Map toJson() => _$EdSheeran$Query$Node$Artist$ReleaseConnectionToJson(this); } @JsonSerializable(explicitToJson: true) class EdSheeran$Query$Node$Artist$LifeSpan extends JsonSerializable with EquatableMixin { EdSheeran$Query$Node$Artist$LifeSpan(); factory EdSheeran$Query$Node$Artist$LifeSpan.fromJson( Map json) => _$EdSheeran$Query$Node$Artist$LifeSpanFromJson(json); DateTime? begin; @override List get props => [begin]; @override Map toJson() => _$EdSheeran$Query$Node$Artist$LifeSpanToJson(this); } @JsonSerializable(explicitToJson: true) class EdSheeran$Query$Node$Artist$SpotifyArtist extends JsonSerializable with EquatableMixin { EdSheeran$Query$Node$Artist$SpotifyArtist(); factory EdSheeran$Query$Node$Artist$SpotifyArtist.fromJson( Map json) => _$EdSheeran$Query$Node$Artist$SpotifyArtistFromJson(json); late String href; @override List get props => [href]; @override Map toJson() => _$EdSheeran$Query$Node$Artist$SpotifyArtistToJson(this); } @JsonSerializable(explicitToJson: true) class EdSheeran$Query$Node$Artist extends EdSheeran$Query$Node with EquatableMixin { EdSheeran$Query$Node$Artist(); factory EdSheeran$Query$Node$Artist.fromJson(Map json) => _$EdSheeran$Query$Node$ArtistFromJson(json); late String mbid; String? name; EdSheeran$Query$Node$Artist$ReleaseConnection? releases; EdSheeran$Query$Node$Artist$LifeSpan? lifeSpan; EdSheeran$Query$Node$Artist$SpotifyArtist? spotify; @override List get props => [mbid, name, releases, lifeSpan, spotify]; @override Map toJson() => _$EdSheeran$Query$Node$ArtistToJson(this); } @JsonSerializable(explicitToJson: true) class EdSheeran$Query$Node extends JsonSerializable with EquatableMixin { EdSheeran$Query$Node(); factory EdSheeran$Query$Node.fromJson(Map json) { switch (json['__typename'].toString()) { case r'Artist': return EdSheeran$Query$Node$Artist.fromJson(json); default: } return _$EdSheeran$Query$NodeFromJson(json); } @JsonKey(name: '__typename') String? $$typename; late String id; @override List get props => [$$typename, id]; @override Map toJson() { switch ($$typename) { case r'Artist': return (this as EdSheeran$Query$Node$Artist).toJson(); default: } return _$EdSheeran$Query$NodeToJson(this); } } @JsonSerializable(explicitToJson: true) class EdSheeran$Query extends JsonSerializable with EquatableMixin { EdSheeran$Query(); factory EdSheeran$Query.fromJson(Map json) => _$EdSheeran$QueryFromJson(json); EdSheeran$Query$Node? node; @override List get props => [node]; @override Map toJson() => _$EdSheeran$QueryToJson(this); } enum ReleaseStatus { @JsonValue('OFFICIAL') official, @JsonValue('PROMOTION') promotion, @JsonValue('BOOTLEG') bootleg, @JsonValue('PSEUDORELEASE') pseudorelease, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } final ED_SHEERAN_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'ed_sheeran'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'node'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'id'), value: StringValueNode( value: 'QXJ0aXN0OmI4YTdjNTFmLTM2MmMtNGRjYi1hMjU5LWJjNmUwMDk1ZjBhNg==', isBlock: false)) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: '__typename'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null), InlineFragmentNode( typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Artist'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mbid'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'releases'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'nodes'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'status'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ])), FieldNode( name: NameNode(value: 'lifeSpan'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'begin'), alias: null, arguments: [], directives: [], selectionSet: null) ])), FieldNode( name: NameNode(value: 'spotify'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'href'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ])) ])) ])) ]); class EdSheeranQuery extends GraphQLQuery { EdSheeranQuery(); @override final DocumentNode document = ED_SHEERAN_QUERY_DOCUMENT; @override final String operationName = 'ed_sheeran'; @override List get props => [document, operationName]; @override EdSheeran$Query parse(Map json) => EdSheeran$Query.fromJson(json); } ================================================ FILE: example/graphbrainz/lib/queries/ed_sheeran.query.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'ed_sheeran.query.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** EdSheeran$Query$Node$Artist$ReleaseConnection$Release _$EdSheeran$Query$Node$Artist$ReleaseConnection$ReleaseFromJson( Map json) => EdSheeran$Query$Node$Artist$ReleaseConnection$Release() ..id = json['id'] as String ..status = _$enumDecodeNullable( _$ReleaseStatusEnumMap, json['status'], unknownValue: ReleaseStatus.artemisUnknown); Map _$EdSheeran$Query$Node$Artist$ReleaseConnection$ReleaseToJson( EdSheeran$Query$Node$Artist$ReleaseConnection$Release instance) => { 'id': instance.id, 'status': _$ReleaseStatusEnumMap[instance.status], }; K _$enumDecode( Map enumValues, Object? source, { K? unknownValue, }) { if (source == null) { throw ArgumentError( 'A value must be provided. Supported values: ' '${enumValues.values.join(', ')}', ); } return enumValues.entries.singleWhere( (e) => e.value == source, orElse: () { if (unknownValue == null) { throw ArgumentError( '`$source` is not one of the supported values: ' '${enumValues.values.join(', ')}', ); } return MapEntry(unknownValue, enumValues.values.first); }, ).key; } K? _$enumDecodeNullable( Map enumValues, dynamic source, { K? unknownValue, }) { if (source == null) { return null; } return _$enumDecode(enumValues, source, unknownValue: unknownValue); } const _$ReleaseStatusEnumMap = { ReleaseStatus.official: 'OFFICIAL', ReleaseStatus.promotion: 'PROMOTION', ReleaseStatus.bootleg: 'BOOTLEG', ReleaseStatus.pseudorelease: 'PSEUDORELEASE', ReleaseStatus.artemisUnknown: 'ARTEMIS_UNKNOWN', }; EdSheeran$Query$Node$Artist$ReleaseConnection _$EdSheeran$Query$Node$Artist$ReleaseConnectionFromJson( Map json) => EdSheeran$Query$Node$Artist$ReleaseConnection() ..nodes = (json['nodes'] as List?) ?.map((e) => e == null ? null : EdSheeran$Query$Node$Artist$ReleaseConnection$Release .fromJson(e as Map)) .toList(); Map _$EdSheeran$Query$Node$Artist$ReleaseConnectionToJson( EdSheeran$Query$Node$Artist$ReleaseConnection instance) => { 'nodes': instance.nodes?.map((e) => e?.toJson()).toList(), }; EdSheeran$Query$Node$Artist$LifeSpan _$EdSheeran$Query$Node$Artist$LifeSpanFromJson(Map json) => EdSheeran$Query$Node$Artist$LifeSpan() ..begin = json['begin'] == null ? null : DateTime.parse(json['begin'] as String); Map _$EdSheeran$Query$Node$Artist$LifeSpanToJson( EdSheeran$Query$Node$Artist$LifeSpan instance) => { 'begin': instance.begin?.toIso8601String(), }; EdSheeran$Query$Node$Artist$SpotifyArtist _$EdSheeran$Query$Node$Artist$SpotifyArtistFromJson( Map json) => EdSheeran$Query$Node$Artist$SpotifyArtist() ..href = json['href'] as String; Map _$EdSheeran$Query$Node$Artist$SpotifyArtistToJson( EdSheeran$Query$Node$Artist$SpotifyArtist instance) => { 'href': instance.href, }; EdSheeran$Query$Node$Artist _$EdSheeran$Query$Node$ArtistFromJson( Map json) => EdSheeran$Query$Node$Artist() ..$$typename = json['__typename'] as String? ..id = json['id'] as String ..mbid = json['mbid'] as String ..name = json['name'] as String? ..releases = json['releases'] == null ? null : EdSheeran$Query$Node$Artist$ReleaseConnection.fromJson( json['releases'] as Map) ..lifeSpan = json['lifeSpan'] == null ? null : EdSheeran$Query$Node$Artist$LifeSpan.fromJson( json['lifeSpan'] as Map) ..spotify = json['spotify'] == null ? null : EdSheeran$Query$Node$Artist$SpotifyArtist.fromJson( json['spotify'] as Map); Map _$EdSheeran$Query$Node$ArtistToJson( EdSheeran$Query$Node$Artist instance) => { '__typename': instance.$$typename, 'id': instance.id, 'mbid': instance.mbid, 'name': instance.name, 'releases': instance.releases?.toJson(), 'lifeSpan': instance.lifeSpan?.toJson(), 'spotify': instance.spotify?.toJson(), }; EdSheeran$Query$Node _$EdSheeran$Query$NodeFromJson( Map json) => EdSheeran$Query$Node() ..$$typename = json['__typename'] as String? ..id = json['id'] as String; Map _$EdSheeran$Query$NodeToJson( EdSheeran$Query$Node instance) => { '__typename': instance.$$typename, 'id': instance.id, }; EdSheeran$Query _$EdSheeran$QueryFromJson(Map json) => EdSheeran$Query() ..node = json['node'] == null ? null : EdSheeran$Query$Node.fromJson(json['node'] as Map); Map _$EdSheeran$QueryToJson(EdSheeran$Query instance) => { 'node': instance.node?.toJson(), }; ================================================ FILE: example/graphbrainz/pubspec.yaml ================================================ name: graphbrainz_example version: 0.0.1 environment: sdk: ">=2.12.0 <3.0.0" dependencies: http: intl: dev_dependencies: test: build_runner: json_serializable: lints: ^1.0.1 artemis: path: ../../. ================================================ FILE: example/hasura/build.yaml ================================================ targets: $default: sources: - lib/** - graphql/** - schema.graphql builders: artemis: options: # fragments_glob: graphql/**.fragment.graphql schema_mapping: - schema: schema.graphql queries_glob: graphql/messages_with_users.graphql output: lib/graphql/messages_with_users.graphql.dart ================================================ FILE: example/hasura/graphql/messages_with_users.graphql ================================================ subscription messages_with_users { messages { id message profile { id name } } } ================================================ FILE: example/hasura/hasura.sql ================================================ CREATE TABLE profile ( id INTEGER PRIMARY KEY, name TEXT ) CREATE TABLE messages ( id INT PRIMARY KEY, message TEXT, profile_id INTEGER REFERENCES profile(id), ) ================================================ FILE: example/hasura/lib/graphql/messages_with_users.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'messages_with_users.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class MessagesWithUsers$SubscriptionRoot$Messages$Profile extends JsonSerializable with EquatableMixin { MessagesWithUsers$SubscriptionRoot$Messages$Profile(); factory MessagesWithUsers$SubscriptionRoot$Messages$Profile.fromJson( Map json) => _$MessagesWithUsers$SubscriptionRoot$Messages$ProfileFromJson(json); late int id; late String name; @override List get props => [id, name]; @override Map toJson() => _$MessagesWithUsers$SubscriptionRoot$Messages$ProfileToJson(this); } @JsonSerializable(explicitToJson: true) class MessagesWithUsers$SubscriptionRoot$Messages extends JsonSerializable with EquatableMixin { MessagesWithUsers$SubscriptionRoot$Messages(); factory MessagesWithUsers$SubscriptionRoot$Messages.fromJson( Map json) => _$MessagesWithUsers$SubscriptionRoot$MessagesFromJson(json); late int id; late String message; late MessagesWithUsers$SubscriptionRoot$Messages$Profile profile; @override List get props => [id, message, profile]; @override Map toJson() => _$MessagesWithUsers$SubscriptionRoot$MessagesToJson(this); } @JsonSerializable(explicitToJson: true) class MessagesWithUsers$SubscriptionRoot extends JsonSerializable with EquatableMixin { MessagesWithUsers$SubscriptionRoot(); factory MessagesWithUsers$SubscriptionRoot.fromJson( Map json) => _$MessagesWithUsers$SubscriptionRootFromJson(json); late List messages; @override List get props => [messages]; @override Map toJson() => _$MessagesWithUsers$SubscriptionRootToJson(this); } final MESSAGES_WITH_USERS_SUBSCRIPTION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.subscription, name: NameNode(value: 'messages_with_users'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'messages'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'message'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'profile'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ])) ])) ]); class MessagesWithUsersSubscription extends GraphQLQuery { MessagesWithUsersSubscription(); @override final DocumentNode document = MESSAGES_WITH_USERS_SUBSCRIPTION_DOCUMENT; @override final String operationName = 'messages_with_users'; @override List get props => [document, operationName]; @override MessagesWithUsers$SubscriptionRoot parse(Map json) => MessagesWithUsers$SubscriptionRoot.fromJson(json); } ================================================ FILE: example/hasura/lib/graphql/messages_with_users.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'messages_with_users.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** MessagesWithUsers$SubscriptionRoot$Messages$Profile _$MessagesWithUsers$SubscriptionRoot$Messages$ProfileFromJson( Map json) => MessagesWithUsers$SubscriptionRoot$Messages$Profile() ..id = json['id'] as int ..name = json['name'] as String; Map _$MessagesWithUsers$SubscriptionRoot$Messages$ProfileToJson( MessagesWithUsers$SubscriptionRoot$Messages$Profile instance) => { 'id': instance.id, 'name': instance.name, }; MessagesWithUsers$SubscriptionRoot$Messages _$MessagesWithUsers$SubscriptionRoot$MessagesFromJson( Map json) => MessagesWithUsers$SubscriptionRoot$Messages() ..id = json['id'] as int ..message = json['message'] as String ..profile = MessagesWithUsers$SubscriptionRoot$Messages$Profile.fromJson( json['profile'] as Map); Map _$MessagesWithUsers$SubscriptionRoot$MessagesToJson( MessagesWithUsers$SubscriptionRoot$Messages instance) => { 'id': instance.id, 'message': instance.message, 'profile': instance.profile.toJson(), }; MessagesWithUsers$SubscriptionRoot _$MessagesWithUsers$SubscriptionRootFromJson( Map json) => MessagesWithUsers$SubscriptionRoot() ..messages = (json['messages'] as List) .map((e) => MessagesWithUsers$SubscriptionRoot$Messages.fromJson( e as Map)) .toList(); Map _$MessagesWithUsers$SubscriptionRootToJson( MessagesWithUsers$SubscriptionRoot instance) => { 'messages': instance.messages.map((e) => e.toJson()).toList(), }; ================================================ FILE: example/hasura/lib/main.dart ================================================ import 'dart:async'; import 'package:artemis/artemis.dart'; import 'package:gql_link/gql_link.dart'; import 'package:gql_websocket_link/gql_websocket_link.dart'; import 'graphql/messages_with_users.graphql.dart'; Future main() async { final client = ArtemisClient.fromLink( Link.from([ WebSocketLink('ws://localhost:8080/v1/graphql', autoReconnect: true), ]), ); final messagesWithUsers = MessagesWithUsersSubscription(); client.stream(messagesWithUsers).listen((response) { print(response.data?.messages.last.message); }); } ================================================ FILE: example/hasura/pubspec.yaml ================================================ name: hasura_example version: 0.0.1 environment: sdk: ">=2.12.0 <3.0.0" dependencies: http: gql_websocket_link: dev_dependencies: test: lints: ^1.0.1 build_runner: json_serializable: artemis: path: ../../. ================================================ FILE: example/hasura/schema.graphql ================================================ schema { query: query_root mutation: mutation_root subscription: subscription_root } """ expression to compare columns of type Int. All fields are combined with logical 'AND'. """ input Int_comparison_exp { _eq: Int _gt: Int _gte: Int _in: [Int!] _is_null: Boolean _lt: Int _lte: Int _neq: Int _nin: [Int!] } """ columns and relationships of "messages" """ type messages { id: Int! message: String! """An object relationship""" profile: profile! profile_id: Int! } """ aggregated selection of "messages" """ type messages_aggregate { aggregate: messages_aggregate_fields nodes: [messages!]! } """ aggregate fields of "messages" """ type messages_aggregate_fields { avg: messages_avg_fields count(columns: [messages_select_column!], distinct: Boolean): Int max: messages_max_fields min: messages_min_fields stddev: messages_stddev_fields stddev_pop: messages_stddev_pop_fields stddev_samp: messages_stddev_samp_fields sum: messages_sum_fields var_pop: messages_var_pop_fields var_samp: messages_var_samp_fields variance: messages_variance_fields } """ order by aggregate values of table "messages" """ input messages_aggregate_order_by { avg: messages_avg_order_by count: order_by max: messages_max_order_by min: messages_min_order_by stddev: messages_stddev_order_by stddev_pop: messages_stddev_pop_order_by stddev_samp: messages_stddev_samp_order_by sum: messages_sum_order_by var_pop: messages_var_pop_order_by var_samp: messages_var_samp_order_by variance: messages_variance_order_by } """ input type for inserting array relation for remote table "messages" """ input messages_arr_rel_insert_input { data: [messages_insert_input!]! on_conflict: messages_on_conflict } """aggregate avg on columns""" type messages_avg_fields { id: Float profile_id: Float } """ order by avg() on columns of table "messages" """ input messages_avg_order_by { id: order_by profile_id: order_by } """ Boolean expression to filter rows from the table "messages". All fields are combined with a logical 'AND'. """ input messages_bool_exp { _and: [messages_bool_exp] _not: messages_bool_exp _or: [messages_bool_exp] id: Int_comparison_exp message: String_comparison_exp profile: profile_bool_exp profile_id: Int_comparison_exp } """ unique or primary key constraints on table "messages" """ enum messages_constraint { """unique or primary key constraint""" messages_pkey } """ input type for incrementing integer column in table "messages" """ input messages_inc_input { id: Int profile_id: Int } """ input type for inserting data into table "messages" """ input messages_insert_input { id: Int message: String profile: profile_obj_rel_insert_input profile_id: Int } """aggregate max on columns""" type messages_max_fields { id: Int message: String profile_id: Int } """ order by max() on columns of table "messages" """ input messages_max_order_by { id: order_by message: order_by profile_id: order_by } """aggregate min on columns""" type messages_min_fields { id: Int message: String profile_id: Int } """ order by min() on columns of table "messages" """ input messages_min_order_by { id: order_by message: order_by profile_id: order_by } """ response of any mutation on the table "messages" """ type messages_mutation_response { """number of affected rows by the mutation""" affected_rows: Int! """data of the affected rows by the mutation""" returning: [messages!]! } """ input type for inserting object relation for remote table "messages" """ input messages_obj_rel_insert_input { data: messages_insert_input! on_conflict: messages_on_conflict } """ on conflict condition type for table "messages" """ input messages_on_conflict { constraint: messages_constraint! update_columns: [messages_update_column!]! where: messages_bool_exp } """ ordering options when selecting data from "messages" """ input messages_order_by { id: order_by message: order_by profile: profile_order_by profile_id: order_by } """ primary key columns input for table: "messages" """ input messages_pk_columns_input { id: Int! } """ select columns of table "messages" """ enum messages_select_column { """column name""" id """column name""" message """column name""" profile_id } """ input type for updating data in table "messages" """ input messages_set_input { id: Int message: String profile_id: Int } """aggregate stddev on columns""" type messages_stddev_fields { id: Float profile_id: Float } """ order by stddev() on columns of table "messages" """ input messages_stddev_order_by { id: order_by profile_id: order_by } """aggregate stddev_pop on columns""" type messages_stddev_pop_fields { id: Float profile_id: Float } """ order by stddev_pop() on columns of table "messages" """ input messages_stddev_pop_order_by { id: order_by profile_id: order_by } """aggregate stddev_samp on columns""" type messages_stddev_samp_fields { id: Float profile_id: Float } """ order by stddev_samp() on columns of table "messages" """ input messages_stddev_samp_order_by { id: order_by profile_id: order_by } """aggregate sum on columns""" type messages_sum_fields { id: Int profile_id: Int } """ order by sum() on columns of table "messages" """ input messages_sum_order_by { id: order_by profile_id: order_by } """ update columns of table "messages" """ enum messages_update_column { """column name""" id """column name""" message """column name""" profile_id } """aggregate var_pop on columns""" type messages_var_pop_fields { id: Float profile_id: Float } """ order by var_pop() on columns of table "messages" """ input messages_var_pop_order_by { id: order_by profile_id: order_by } """aggregate var_samp on columns""" type messages_var_samp_fields { id: Float profile_id: Float } """ order by var_samp() on columns of table "messages" """ input messages_var_samp_order_by { id: order_by profile_id: order_by } """aggregate variance on columns""" type messages_variance_fields { id: Float profile_id: Float } """ order by variance() on columns of table "messages" """ input messages_variance_order_by { id: order_by profile_id: order_by } """mutation root""" type mutation_root { """ delete data from the table: "messages" """ delete_messages( """filter the rows which have to be deleted""" where: messages_bool_exp! ): messages_mutation_response """ delete single row from the table: "messages" """ delete_messages_by_pk(id: Int!): messages """ delete data from the table: "profile" """ delete_profile( """filter the rows which have to be deleted""" where: profile_bool_exp! ): profile_mutation_response """ delete single row from the table: "profile" """ delete_profile_by_pk(id: Int!): profile """ insert data into the table: "messages" """ insert_messages( """the rows to be inserted""" objects: [messages_insert_input!]! """on conflict condition""" on_conflict: messages_on_conflict ): messages_mutation_response """ insert a single row into the table: "messages" """ insert_messages_one( """the row to be inserted""" object: messages_insert_input! """on conflict condition""" on_conflict: messages_on_conflict ): messages """ insert data into the table: "profile" """ insert_profile( """the rows to be inserted""" objects: [profile_insert_input!]! """on conflict condition""" on_conflict: profile_on_conflict ): profile_mutation_response """ insert a single row into the table: "profile" """ insert_profile_one( """the row to be inserted""" object: profile_insert_input! """on conflict condition""" on_conflict: profile_on_conflict ): profile """ update data of the table: "messages" """ update_messages( """increments the integer columns with given value of the filtered values""" _inc: messages_inc_input """sets the columns of the filtered rows to the given values""" _set: messages_set_input """filter the rows which have to be updated""" where: messages_bool_exp! ): messages_mutation_response """ update single row of the table: "messages" """ update_messages_by_pk( """increments the integer columns with given value of the filtered values""" _inc: messages_inc_input """sets the columns of the filtered rows to the given values""" _set: messages_set_input pk_columns: messages_pk_columns_input! ): messages """ update data of the table: "profile" """ update_profile( """increments the integer columns with given value of the filtered values""" _inc: profile_inc_input """sets the columns of the filtered rows to the given values""" _set: profile_set_input """filter the rows which have to be updated""" where: profile_bool_exp! ): profile_mutation_response """ update single row of the table: "profile" """ update_profile_by_pk( """increments the integer columns with given value of the filtered values""" _inc: profile_inc_input """sets the columns of the filtered rows to the given values""" _set: profile_set_input pk_columns: profile_pk_columns_input! ): profile } """column ordering options""" enum order_by { """in the ascending order, nulls last""" asc """in the ascending order, nulls first""" asc_nulls_first """in the ascending order, nulls last""" asc_nulls_last """in the descending order, nulls first""" desc """in the descending order, nulls first""" desc_nulls_first """in the descending order, nulls last""" desc_nulls_last } """ columns and relationships of "profile" """ type profile { id: Int! """An array relationship""" messages( """distinct select on columns""" distinct_on: [messages_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [messages_order_by!] """filter the rows returned""" where: messages_bool_exp ): [messages!]! """An aggregated array relationship""" messages_aggregate( """distinct select on columns""" distinct_on: [messages_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [messages_order_by!] """filter the rows returned""" where: messages_bool_exp ): messages_aggregate! name: String! } """ aggregated selection of "profile" """ type profile_aggregate { aggregate: profile_aggregate_fields nodes: [profile!]! } """ aggregate fields of "profile" """ type profile_aggregate_fields { avg: profile_avg_fields count(columns: [profile_select_column!], distinct: Boolean): Int max: profile_max_fields min: profile_min_fields stddev: profile_stddev_fields stddev_pop: profile_stddev_pop_fields stddev_samp: profile_stddev_samp_fields sum: profile_sum_fields var_pop: profile_var_pop_fields var_samp: profile_var_samp_fields variance: profile_variance_fields } """ order by aggregate values of table "profile" """ input profile_aggregate_order_by { avg: profile_avg_order_by count: order_by max: profile_max_order_by min: profile_min_order_by stddev: profile_stddev_order_by stddev_pop: profile_stddev_pop_order_by stddev_samp: profile_stddev_samp_order_by sum: profile_sum_order_by var_pop: profile_var_pop_order_by var_samp: profile_var_samp_order_by variance: profile_variance_order_by } """ input type for inserting array relation for remote table "profile" """ input profile_arr_rel_insert_input { data: [profile_insert_input!]! on_conflict: profile_on_conflict } """aggregate avg on columns""" type profile_avg_fields { id: Float } """ order by avg() on columns of table "profile" """ input profile_avg_order_by { id: order_by } """ Boolean expression to filter rows from the table "profile". All fields are combined with a logical 'AND'. """ input profile_bool_exp { _and: [profile_bool_exp] _not: profile_bool_exp _or: [profile_bool_exp] id: Int_comparison_exp messages: messages_bool_exp name: String_comparison_exp } """ unique or primary key constraints on table "profile" """ enum profile_constraint { """unique or primary key constraint""" profile_pkey } """ input type for incrementing integer column in table "profile" """ input profile_inc_input { id: Int } """ input type for inserting data into table "profile" """ input profile_insert_input { id: Int messages: messages_arr_rel_insert_input name: String } """aggregate max on columns""" type profile_max_fields { id: Int name: String } """ order by max() on columns of table "profile" """ input profile_max_order_by { id: order_by name: order_by } """aggregate min on columns""" type profile_min_fields { id: Int name: String } """ order by min() on columns of table "profile" """ input profile_min_order_by { id: order_by name: order_by } """ response of any mutation on the table "profile" """ type profile_mutation_response { """number of affected rows by the mutation""" affected_rows: Int! """data of the affected rows by the mutation""" returning: [profile!]! } """ input type for inserting object relation for remote table "profile" """ input profile_obj_rel_insert_input { data: profile_insert_input! on_conflict: profile_on_conflict } """ on conflict condition type for table "profile" """ input profile_on_conflict { constraint: profile_constraint! update_columns: [profile_update_column!]! where: profile_bool_exp } """ ordering options when selecting data from "profile" """ input profile_order_by { id: order_by messages_aggregate: messages_aggregate_order_by name: order_by } """ primary key columns input for table: "profile" """ input profile_pk_columns_input { id: Int! } """ select columns of table "profile" """ enum profile_select_column { """column name""" id """column name""" name } """ input type for updating data in table "profile" """ input profile_set_input { id: Int name: String } """aggregate stddev on columns""" type profile_stddev_fields { id: Float } """ order by stddev() on columns of table "profile" """ input profile_stddev_order_by { id: order_by } """aggregate stddev_pop on columns""" type profile_stddev_pop_fields { id: Float } """ order by stddev_pop() on columns of table "profile" """ input profile_stddev_pop_order_by { id: order_by } """aggregate stddev_samp on columns""" type profile_stddev_samp_fields { id: Float } """ order by stddev_samp() on columns of table "profile" """ input profile_stddev_samp_order_by { id: order_by } """aggregate sum on columns""" type profile_sum_fields { id: Int } """ order by sum() on columns of table "profile" """ input profile_sum_order_by { id: order_by } """ update columns of table "profile" """ enum profile_update_column { """column name""" id """column name""" name } """aggregate var_pop on columns""" type profile_var_pop_fields { id: Float } """ order by var_pop() on columns of table "profile" """ input profile_var_pop_order_by { id: order_by } """aggregate var_samp on columns""" type profile_var_samp_fields { id: Float } """ order by var_samp() on columns of table "profile" """ input profile_var_samp_order_by { id: order_by } """aggregate variance on columns""" type profile_variance_fields { id: Float } """ order by variance() on columns of table "profile" """ input profile_variance_order_by { id: order_by } """query root""" type query_root { """ fetch data from the table: "messages" """ messages( """distinct select on columns""" distinct_on: [messages_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [messages_order_by!] """filter the rows returned""" where: messages_bool_exp ): [messages!]! """ fetch aggregated fields from the table: "messages" """ messages_aggregate( """distinct select on columns""" distinct_on: [messages_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [messages_order_by!] """filter the rows returned""" where: messages_bool_exp ): messages_aggregate! """fetch data from the table: "messages" using primary key columns""" messages_by_pk(id: Int!): messages """ fetch data from the table: "profile" """ profile( """distinct select on columns""" distinct_on: [profile_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [profile_order_by!] """filter the rows returned""" where: profile_bool_exp ): [profile!]! """ fetch aggregated fields from the table: "profile" """ profile_aggregate( """distinct select on columns""" distinct_on: [profile_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [profile_order_by!] """filter the rows returned""" where: profile_bool_exp ): profile_aggregate! """fetch data from the table: "profile" using primary key columns""" profile_by_pk(id: Int!): profile } """ expression to compare columns of type String. All fields are combined with logical 'AND'. """ input String_comparison_exp { _eq: String _gt: String _gte: String _ilike: String _in: [String!] _is_null: Boolean _like: String _lt: String _lte: String _neq: String _nilike: String _nin: [String!] _nlike: String _nsimilar: String _similar: String } """subscription root""" type subscription_root { """ fetch data from the table: "messages" """ messages( """distinct select on columns""" distinct_on: [messages_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [messages_order_by!] """filter the rows returned""" where: messages_bool_exp ): [messages!]! """ fetch aggregated fields from the table: "messages" """ messages_aggregate( """distinct select on columns""" distinct_on: [messages_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [messages_order_by!] """filter the rows returned""" where: messages_bool_exp ): messages_aggregate! """fetch data from the table: "messages" using primary key columns""" messages_by_pk(id: Int!): messages """ fetch data from the table: "profile" """ profile( """distinct select on columns""" distinct_on: [profile_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [profile_order_by!] """filter the rows returned""" where: profile_bool_exp ): [profile!]! """ fetch aggregated fields from the table: "profile" """ profile_aggregate( """distinct select on columns""" distinct_on: [profile_select_column!] """limit the number of rows returned""" limit: Int """skip the first n rows. Use only with order_by""" offset: Int """sort the rows by one or more columns""" order_by: [profile_order_by!] """filter the rows returned""" where: profile_bool_exp ): profile_aggregate! """fetch data from the table: "profile" using primary key columns""" profile_by_pk(id: Int!): profile } ================================================ FILE: example/pokemon/build.yaml ================================================ targets: $default: sources: - lib/** - graphql/** - pokemon.schema.graphql builders: artemis: options: fragments_glob: graphql/**.fragment.graphql schema_mapping: - schema: pokemon.schema.graphql queries_glob: graphql/simple_query.query.graphql output: lib/graphql/simple_query.dart - schema: pokemon.schema.graphql queries_glob: graphql/big_query.query.graphql output: lib/graphql/big_query.dart - schema: pokemon.schema.graphql queries_glob: graphql/fragment_query.query.graphql output: lib/graphql/fragment_query.dart - schema: pokemon.schema.graphql queries_glob: graphql/fragments_glob.query.graphql output: lib/graphql/fragments_glob.dart ================================================ FILE: example/pokemon/graphql/big_query.query.graphql ================================================ query big_query($quantity: Int!) { charmander: pokemon(name: "Charmander") { number types } pokemons(first: $quantity) { number name types evolutions: evolutions { number name } } } ================================================ FILE: example/pokemon/graphql/fragment_query.query.graphql ================================================ query fragmentQuery($quantity: Int!) { charmander: pokemon(name: "Charmander") { ...PokemonParts } pokemons(first: $quantity) { ...PokemonParts evolutions: evolutions { ...PokemonParts } } } ================================================ FILE: example/pokemon/graphql/fragments_glob.fragment.graphql ================================================ fragment Pokemon on Pokemon { id weight { ...weight } attacks { ...pokemonAttack } } fragment PokemonParts on Pokemon { number name types } fragment weight on PokemonDimension { minimum } fragment pokemonAttack on PokemonAttack { special { ...attack } } fragment attack on Attack { name } ================================================ FILE: example/pokemon/graphql/fragments_glob.query.graphql ================================================ { pokemon(name: "Pikachu") { ...Pokemon evolutions { ...Pokemon } } } ================================================ FILE: example/pokemon/graphql/simple_query.query.graphql ================================================ query simple_query { pokemon(name: "Charmander") { number types } } ================================================ FILE: example/pokemon/lib/graphql/big_query.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND export 'big_query.graphql.dart'; ================================================ FILE: example/pokemon/lib/graphql/big_query.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'big_query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class BigQuery$Query$Charmander extends JsonSerializable with EquatableMixin { BigQuery$Query$Charmander(); factory BigQuery$Query$Charmander.fromJson(Map json) => _$BigQuery$Query$CharmanderFromJson(json); String? number; List? types; @override List get props => [number, types]; @override Map toJson() => _$BigQuery$Query$CharmanderToJson(this); } @JsonSerializable(explicitToJson: true) class BigQuery$Query$Pokemon$Evolutions extends JsonSerializable with EquatableMixin { BigQuery$Query$Pokemon$Evolutions(); factory BigQuery$Query$Pokemon$Evolutions.fromJson( Map json) => _$BigQuery$Query$Pokemon$EvolutionsFromJson(json); String? number; String? name; @override List get props => [number, name]; @override Map toJson() => _$BigQuery$Query$Pokemon$EvolutionsToJson(this); } @JsonSerializable(explicitToJson: true) class BigQuery$Query$Pokemon extends JsonSerializable with EquatableMixin { BigQuery$Query$Pokemon(); factory BigQuery$Query$Pokemon.fromJson(Map json) => _$BigQuery$Query$PokemonFromJson(json); String? number; String? name; List? types; List? evolutions; @override List get props => [number, name, types, evolutions]; @override Map toJson() => _$BigQuery$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class BigQuery$Query extends JsonSerializable with EquatableMixin { BigQuery$Query(); factory BigQuery$Query.fromJson(Map json) => _$BigQuery$QueryFromJson(json); BigQuery$Query$Charmander? charmander; List? pokemons; @override List get props => [charmander, pokemons]; @override Map toJson() => _$BigQuery$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class BigQueryArguments extends JsonSerializable with EquatableMixin { BigQueryArguments({required this.quantity}); @override factory BigQueryArguments.fromJson(Map json) => _$BigQueryArgumentsFromJson(json); late int quantity; @override List get props => [quantity]; @override Map toJson() => _$BigQueryArgumentsToJson(this); } final BIG_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'big_query'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'quantity')), type: NamedTypeNode(name: NameNode(value: 'Int'), isNonNull: true), defaultValue: DefaultValueNode(value: null), directives: []) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: NameNode(value: 'charmander'), arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: StringValueNode(value: 'Charmander', isBlock: false)) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'types'), alias: null, arguments: [], directives: [], selectionSet: null) ])), FieldNode( name: NameNode(value: 'pokemons'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'first'), value: VariableNode(name: NameNode(value: 'quantity'))) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'types'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'evolutions'), alias: NameNode(value: 'evolutions'), arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ])) ])) ]); class BigQueryQuery extends GraphQLQuery { BigQueryQuery({required this.variables}); @override final DocumentNode document = BIG_QUERY_QUERY_DOCUMENT; @override final String operationName = 'big_query'; @override final BigQueryArguments variables; @override List get props => [document, operationName, variables]; @override BigQuery$Query parse(Map json) => BigQuery$Query.fromJson(json); } ================================================ FILE: example/pokemon/lib/graphql/big_query.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'big_query.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** BigQuery$Query$Charmander _$BigQuery$Query$CharmanderFromJson( Map json) => BigQuery$Query$Charmander() ..number = json['number'] as String? ..types = (json['types'] as List?)?.map((e) => e as String?).toList(); Map _$BigQuery$Query$CharmanderToJson( BigQuery$Query$Charmander instance) => { 'number': instance.number, 'types': instance.types, }; BigQuery$Query$Pokemon$Evolutions _$BigQuery$Query$Pokemon$EvolutionsFromJson( Map json) => BigQuery$Query$Pokemon$Evolutions() ..number = json['number'] as String? ..name = json['name'] as String?; Map _$BigQuery$Query$Pokemon$EvolutionsToJson( BigQuery$Query$Pokemon$Evolutions instance) => { 'number': instance.number, 'name': instance.name, }; BigQuery$Query$Pokemon _$BigQuery$Query$PokemonFromJson( Map json) => BigQuery$Query$Pokemon() ..number = json['number'] as String? ..name = json['name'] as String? ..types = (json['types'] as List?)?.map((e) => e as String?).toList() ..evolutions = (json['evolutions'] as List?) ?.map((e) => e == null ? null : BigQuery$Query$Pokemon$Evolutions.fromJson( e as Map)) .toList(); Map _$BigQuery$Query$PokemonToJson( BigQuery$Query$Pokemon instance) => { 'number': instance.number, 'name': instance.name, 'types': instance.types, 'evolutions': instance.evolutions?.map((e) => e?.toJson()).toList(), }; BigQuery$Query _$BigQuery$QueryFromJson(Map json) => BigQuery$Query() ..charmander = json['charmander'] == null ? null : BigQuery$Query$Charmander.fromJson( json['charmander'] as Map) ..pokemons = (json['pokemons'] as List?) ?.map((e) => e == null ? null : BigQuery$Query$Pokemon.fromJson(e as Map)) .toList(); Map _$BigQuery$QueryToJson(BigQuery$Query instance) => { 'charmander': instance.charmander?.toJson(), 'pokemons': instance.pokemons?.map((e) => e?.toJson()).toList(), }; BigQueryArguments _$BigQueryArgumentsFromJson(Map json) => BigQueryArguments( quantity: json['quantity'] as int, ); Map _$BigQueryArgumentsToJson(BigQueryArguments instance) => { 'quantity': instance.quantity, }; ================================================ FILE: example/pokemon/lib/graphql/fragment_query.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND export 'fragment_query.graphql.dart'; ================================================ FILE: example/pokemon/lib/graphql/fragment_query.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'fragment_query.graphql.g.dart'; mixin PokemonPartsMixin { String? number; String? name; List? types; } @JsonSerializable(explicitToJson: true) class FragmentQuery$Query$Charmander extends JsonSerializable with EquatableMixin, PokemonPartsMixin { FragmentQuery$Query$Charmander(); factory FragmentQuery$Query$Charmander.fromJson(Map json) => _$FragmentQuery$Query$CharmanderFromJson(json); @override List get props => [number, name, types]; @override Map toJson() => _$FragmentQuery$Query$CharmanderToJson(this); } @JsonSerializable(explicitToJson: true) class FragmentQuery$Query$Pokemon$Evolutions extends JsonSerializable with EquatableMixin, PokemonPartsMixin { FragmentQuery$Query$Pokemon$Evolutions(); factory FragmentQuery$Query$Pokemon$Evolutions.fromJson( Map json) => _$FragmentQuery$Query$Pokemon$EvolutionsFromJson(json); @override List get props => [number, name, types]; @override Map toJson() => _$FragmentQuery$Query$Pokemon$EvolutionsToJson(this); } @JsonSerializable(explicitToJson: true) class FragmentQuery$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonPartsMixin { FragmentQuery$Query$Pokemon(); factory FragmentQuery$Query$Pokemon.fromJson(Map json) => _$FragmentQuery$Query$PokemonFromJson(json); List? evolutions; @override List get props => [number, name, types, evolutions]; @override Map toJson() => _$FragmentQuery$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class FragmentQuery$Query extends JsonSerializable with EquatableMixin { FragmentQuery$Query(); factory FragmentQuery$Query.fromJson(Map json) => _$FragmentQuery$QueryFromJson(json); FragmentQuery$Query$Charmander? charmander; List? pokemons; @override List get props => [charmander, pokemons]; @override Map toJson() => _$FragmentQuery$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class FragmentQueryArguments extends JsonSerializable with EquatableMixin { FragmentQueryArguments({required this.quantity}); @override factory FragmentQueryArguments.fromJson(Map json) => _$FragmentQueryArgumentsFromJson(json); late int quantity; @override List get props => [quantity]; @override Map toJson() => _$FragmentQueryArgumentsToJson(this); } final FRAGMENT_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'fragmentQuery'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'quantity')), type: NamedTypeNode(name: NameNode(value: 'Int'), isNonNull: true), defaultValue: DefaultValueNode(value: null), directives: []) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: NameNode(value: 'charmander'), arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: StringValueNode(value: 'Charmander', isBlock: false)) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'PokemonParts'), directives: []) ])), FieldNode( name: NameNode(value: 'pokemons'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'first'), value: VariableNode(name: NameNode(value: 'quantity'))) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'PokemonParts'), directives: []), FieldNode( name: NameNode(value: 'evolutions'), alias: NameNode(value: 'evolutions'), arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'PokemonParts'), directives: []) ])) ])) ])), FragmentDefinitionNode( name: NameNode(value: 'PokemonParts'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Pokemon'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'types'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ]); class FragmentQueryQuery extends GraphQLQuery { FragmentQueryQuery({required this.variables}); @override final DocumentNode document = FRAGMENT_QUERY_QUERY_DOCUMENT; @override final String operationName = 'fragmentQuery'; @override final FragmentQueryArguments variables; @override List get props => [document, operationName, variables]; @override FragmentQuery$Query parse(Map json) => FragmentQuery$Query.fromJson(json); } ================================================ FILE: example/pokemon/lib/graphql/fragment_query.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'fragment_query.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** FragmentQuery$Query$Charmander _$FragmentQuery$Query$CharmanderFromJson( Map json) => FragmentQuery$Query$Charmander() ..number = json['number'] as String? ..name = json['name'] as String? ..types = (json['types'] as List?)?.map((e) => e as String?).toList(); Map _$FragmentQuery$Query$CharmanderToJson( FragmentQuery$Query$Charmander instance) => { 'number': instance.number, 'name': instance.name, 'types': instance.types, }; FragmentQuery$Query$Pokemon$Evolutions _$FragmentQuery$Query$Pokemon$EvolutionsFromJson( Map json) => FragmentQuery$Query$Pokemon$Evolutions() ..number = json['number'] as String? ..name = json['name'] as String? ..types = (json['types'] as List?) ?.map((e) => e as String?) .toList(); Map _$FragmentQuery$Query$Pokemon$EvolutionsToJson( FragmentQuery$Query$Pokemon$Evolutions instance) => { 'number': instance.number, 'name': instance.name, 'types': instance.types, }; FragmentQuery$Query$Pokemon _$FragmentQuery$Query$PokemonFromJson( Map json) => FragmentQuery$Query$Pokemon() ..number = json['number'] as String? ..name = json['name'] as String? ..types = (json['types'] as List?)?.map((e) => e as String?).toList() ..evolutions = (json['evolutions'] as List?) ?.map((e) => e == null ? null : FragmentQuery$Query$Pokemon$Evolutions.fromJson( e as Map)) .toList(); Map _$FragmentQuery$Query$PokemonToJson( FragmentQuery$Query$Pokemon instance) => { 'number': instance.number, 'name': instance.name, 'types': instance.types, 'evolutions': instance.evolutions?.map((e) => e?.toJson()).toList(), }; FragmentQuery$Query _$FragmentQuery$QueryFromJson(Map json) => FragmentQuery$Query() ..charmander = json['charmander'] == null ? null : FragmentQuery$Query$Charmander.fromJson( json['charmander'] as Map) ..pokemons = (json['pokemons'] as List?) ?.map((e) => e == null ? null : FragmentQuery$Query$Pokemon.fromJson(e as Map)) .toList(); Map _$FragmentQuery$QueryToJson( FragmentQuery$Query instance) => { 'charmander': instance.charmander?.toJson(), 'pokemons': instance.pokemons?.map((e) => e?.toJson()).toList(), }; FragmentQueryArguments _$FragmentQueryArgumentsFromJson( Map json) => FragmentQueryArguments( quantity: json['quantity'] as int, ); Map _$FragmentQueryArgumentsToJson( FragmentQueryArguments instance) => { 'quantity': instance.quantity, }; ================================================ FILE: example/pokemon/lib/graphql/fragments_glob.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND export 'fragments_glob.graphql.dart'; ================================================ FILE: example/pokemon/lib/graphql/fragments_glob.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'fragments_glob.graphql.g.dart'; mixin PokemonMixin { late String id; PokemonMixin$PokemonDimension? weight; PokemonMixin$PokemonAttack? attacks; } mixin WeightMixin { String? minimum; } mixin PokemonAttackMixin { List? special; } mixin AttackMixin { String? name; } @JsonSerializable(explicitToJson: true) class FragmentsGlob$Query$Pokemon$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin { FragmentsGlob$Query$Pokemon$Pokemon(); factory FragmentsGlob$Query$Pokemon$Pokemon.fromJson( Map json) => _$FragmentsGlob$Query$Pokemon$PokemonFromJson(json); @override List get props => [id, weight, attacks]; @override Map toJson() => _$FragmentsGlob$Query$Pokemon$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class FragmentsGlob$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin { FragmentsGlob$Query$Pokemon(); factory FragmentsGlob$Query$Pokemon.fromJson(Map json) => _$FragmentsGlob$Query$PokemonFromJson(json); List? evolutions; @override List get props => [id, weight, attacks, evolutions]; @override Map toJson() => _$FragmentsGlob$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class FragmentsGlob$Query extends JsonSerializable with EquatableMixin { FragmentsGlob$Query(); factory FragmentsGlob$Query.fromJson(Map json) => _$FragmentsGlob$QueryFromJson(json); FragmentsGlob$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$FragmentsGlob$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$PokemonDimension extends JsonSerializable with EquatableMixin, WeightMixin { PokemonMixin$PokemonDimension(); factory PokemonMixin$PokemonDimension.fromJson(Map json) => _$PokemonMixin$PokemonDimensionFromJson(json); @override List get props => [minimum]; @override Map toJson() => _$PokemonMixin$PokemonDimensionToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$PokemonAttack extends JsonSerializable with EquatableMixin, PokemonAttackMixin { PokemonMixin$PokemonAttack(); factory PokemonMixin$PokemonAttack.fromJson(Map json) => _$PokemonMixin$PokemonAttackFromJson(json); @override List get props => [special]; @override Map toJson() => _$PokemonMixin$PokemonAttackToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonAttackMixin$Attack extends JsonSerializable with EquatableMixin, AttackMixin { PokemonAttackMixin$Attack(); factory PokemonAttackMixin$Attack.fromJson(Map json) => _$PokemonAttackMixin$AttackFromJson(json); @override List get props => [name]; @override Map toJson() => _$PokemonAttackMixin$AttackToJson(this); } final FRAGMENTS_GLOB_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: null, variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: StringValueNode(value: 'Pikachu', isBlock: false)) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Pokemon'), directives: []), FieldNode( name: NameNode(value: 'evolutions'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Pokemon'), directives: []) ])) ])) ])), FragmentDefinitionNode( name: NameNode(value: 'Pokemon'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Pokemon'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'weight'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'weight'), directives: []) ])), FieldNode( name: NameNode(value: 'attacks'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'pokemonAttack'), directives: []) ])) ])), FragmentDefinitionNode( name: NameNode(value: 'weight'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'PokemonDimension'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'minimum'), alias: null, arguments: [], directives: [], selectionSet: null) ])), FragmentDefinitionNode( name: NameNode(value: 'pokemonAttack'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'PokemonAttack'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'special'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'attack'), directives: []) ])) ])), FragmentDefinitionNode( name: NameNode(value: 'attack'), typeCondition: TypeConditionNode( on: NamedTypeNode(name: NameNode(value: 'Attack'), isNonNull: false)), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ]); class FragmentsGlobQuery extends GraphQLQuery { FragmentsGlobQuery(); @override final DocumentNode document = FRAGMENTS_GLOB_QUERY_DOCUMENT; @override final String operationName = 'fragments_glob'; @override List get props => [document, operationName]; @override FragmentsGlob$Query parse(Map json) => FragmentsGlob$Query.fromJson(json); } ================================================ FILE: example/pokemon/lib/graphql/fragments_glob.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'fragments_glob.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** FragmentsGlob$Query$Pokemon$Pokemon _$FragmentsGlob$Query$Pokemon$PokemonFromJson(Map json) => FragmentsGlob$Query$Pokemon$Pokemon() ..id = json['id'] as String ..weight = json['weight'] == null ? null : PokemonMixin$PokemonDimension.fromJson( json['weight'] as Map) ..attacks = json['attacks'] == null ? null : PokemonMixin$PokemonAttack.fromJson( json['attacks'] as Map); Map _$FragmentsGlob$Query$Pokemon$PokemonToJson( FragmentsGlob$Query$Pokemon$Pokemon instance) => { 'id': instance.id, 'weight': instance.weight?.toJson(), 'attacks': instance.attacks?.toJson(), }; FragmentsGlob$Query$Pokemon _$FragmentsGlob$Query$PokemonFromJson( Map json) => FragmentsGlob$Query$Pokemon() ..id = json['id'] as String ..weight = json['weight'] == null ? null : PokemonMixin$PokemonDimension.fromJson( json['weight'] as Map) ..attacks = json['attacks'] == null ? null : PokemonMixin$PokemonAttack.fromJson( json['attacks'] as Map) ..evolutions = (json['evolutions'] as List?) ?.map((e) => e == null ? null : FragmentsGlob$Query$Pokemon$Pokemon.fromJson( e as Map)) .toList(); Map _$FragmentsGlob$Query$PokemonToJson( FragmentsGlob$Query$Pokemon instance) => { 'id': instance.id, 'weight': instance.weight?.toJson(), 'attacks': instance.attacks?.toJson(), 'evolutions': instance.evolutions?.map((e) => e?.toJson()).toList(), }; FragmentsGlob$Query _$FragmentsGlob$QueryFromJson(Map json) => FragmentsGlob$Query() ..pokemon = json['pokemon'] == null ? null : FragmentsGlob$Query$Pokemon.fromJson( json['pokemon'] as Map); Map _$FragmentsGlob$QueryToJson( FragmentsGlob$Query instance) => { 'pokemon': instance.pokemon?.toJson(), }; PokemonMixin$PokemonDimension _$PokemonMixin$PokemonDimensionFromJson( Map json) => PokemonMixin$PokemonDimension()..minimum = json['minimum'] as String?; Map _$PokemonMixin$PokemonDimensionToJson( PokemonMixin$PokemonDimension instance) => { 'minimum': instance.minimum, }; PokemonMixin$PokemonAttack _$PokemonMixin$PokemonAttackFromJson( Map json) => PokemonMixin$PokemonAttack() ..special = (json['special'] as List?) ?.map((e) => e == null ? null : PokemonAttackMixin$Attack.fromJson(e as Map)) .toList(); Map _$PokemonMixin$PokemonAttackToJson( PokemonMixin$PokemonAttack instance) => { 'special': instance.special?.map((e) => e?.toJson()).toList(), }; PokemonAttackMixin$Attack _$PokemonAttackMixin$AttackFromJson( Map json) => PokemonAttackMixin$Attack()..name = json['name'] as String?; Map _$PokemonAttackMixin$AttackToJson( PokemonAttackMixin$Attack instance) => { 'name': instance.name, }; ================================================ FILE: example/pokemon/lib/graphql/simple_query.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND export 'simple_query.graphql.dart'; ================================================ FILE: example/pokemon/lib/graphql/simple_query.graphql.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart = 2.12 import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'simple_query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SimpleQuery$Query$Pokemon extends JsonSerializable with EquatableMixin { SimpleQuery$Query$Pokemon(); factory SimpleQuery$Query$Pokemon.fromJson(Map json) => _$SimpleQuery$Query$PokemonFromJson(json); String? number; List? types; @override List get props => [number, types]; @override Map toJson() => _$SimpleQuery$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class SimpleQuery$Query extends JsonSerializable with EquatableMixin { SimpleQuery$Query(); factory SimpleQuery$Query.fromJson(Map json) => _$SimpleQuery$QueryFromJson(json); SimpleQuery$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$SimpleQuery$QueryToJson(this); } final SIMPLE_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'simple_query'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: StringValueNode(value: 'Charmander', isBlock: false)) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( name: NameNode(value: 'types'), alias: null, arguments: [], directives: [], selectionSet: null) ])) ])) ]); class SimpleQueryQuery extends GraphQLQuery { SimpleQueryQuery(); @override final DocumentNode document = SIMPLE_QUERY_QUERY_DOCUMENT; @override final String operationName = 'simple_query'; @override List get props => [document, operationName]; @override SimpleQuery$Query parse(Map json) => SimpleQuery$Query.fromJson(json); } ================================================ FILE: example/pokemon/lib/graphql/simple_query.graphql.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND // @dart=2.12 part of 'simple_query.graphql.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SimpleQuery$Query$Pokemon _$SimpleQuery$Query$PokemonFromJson( Map json) => SimpleQuery$Query$Pokemon() ..number = json['number'] as String? ..types = (json['types'] as List?)?.map((e) => e as String?).toList(); Map _$SimpleQuery$Query$PokemonToJson( SimpleQuery$Query$Pokemon instance) => { 'number': instance.number, 'types': instance.types, }; SimpleQuery$Query _$SimpleQuery$QueryFromJson(Map json) => SimpleQuery$Query() ..pokemon = json['pokemon'] == null ? null : SimpleQuery$Query$Pokemon.fromJson( json['pokemon'] as Map); Map _$SimpleQuery$QueryToJson(SimpleQuery$Query instance) => { 'pokemon': instance.pokemon?.toJson(), }; ================================================ FILE: example/pokemon/lib/main.dart ================================================ import 'dart:async'; import 'package:artemis/artemis.dart'; import 'graphql/big_query.dart'; import 'graphql/simple_query.dart'; Future main() async { final client = ArtemisClient( 'https://graphql-pokemon2.vercel.app', ); final simpleQuery = SimpleQueryQuery(); final bigQuery = BigQueryQuery(variables: BigQueryArguments(quantity: 5)); final bigQuery2 = BigQueryQuery(variables: BigQueryArguments(quantity: 5)); print('Equality works: ${bigQuery == bigQuery2}'); final simpleQueryResponse = await client.execute(simpleQuery); final bigQueryResponse = await client.execute(bigQuery); client.dispose(); print('Simple query response: ${simpleQueryResponse.data?.pokemon?.number}'); for (final pokemon in bigQueryResponse.data?.pokemons ?? []) { print('#${pokemon.number}: ${pokemon.name}'); } } ================================================ FILE: example/pokemon/pokemon.schema.graphql ================================================ """Represents a Pokémon's attack types""" type Attack { """The name of this Pokémon attack""" name: String """The type of this Pokémon attack""" type: String """The damage of this Pokémon attack""" damage: Int } """Represents a Pokémon""" type Pokemon { """The ID of an object""" id: ID! """The identifier of this Pokémon""" number: String """The name of this Pokémon""" name: String """The minimum and maximum weight of this Pokémon""" weight: PokemonDimension """The minimum and maximum weight of this Pokémon""" height: PokemonDimension """The classification of this Pokémon""" classification: String """The type(s) of this Pokémon""" types: [String] """The type(s) of Pokémons that this Pokémon is resistant to""" resistant: [String] """The attacks of this Pokémon""" attacks: PokemonAttack """The type(s) of Pokémons that this Pokémon weak to""" weaknesses: [String] fleeRate: Float """The maximum CP of this Pokémon""" maxCP: Int """The evolutions of this Pokémon""" evolutions: [Pokemon] """The evolution requirements of this Pokémon""" evolutionRequirements: PokemonEvolutionRequirement """The maximum HP of this Pokémon""" maxHP: Int image: String } """Represents a Pokémon's attack types""" type PokemonAttack { """The fast attacks of this Pokémon""" fast: [Attack] """The special attacks of this Pokémon""" special: [Attack] } """Represents a Pokémon's dimensions""" type PokemonDimension { """The minimum value of this dimension""" minimum: String """The maximum value of this dimension""" maximum: String } """Represents a Pokémon's requirement to evolve""" type PokemonEvolutionRequirement { """The amount of candy to evolve""" amount: Int """The name of the candy to evolve""" name: String } """Query any Pokémon by number or name""" type Query { query: Query pokemons(first: Int!): [Pokemon] pokemon(id: String, name: String): Pokemon } ================================================ FILE: example/pokemon/pubspec.yaml ================================================ name: pokemon_example version: 0.0.1 environment: sdk: ">=2.12.0 <3.0.0" dependencies: http: dev_dependencies: test: build_runner: json_serializable: lints: ^1.0.1 artemis: path: ../../. ================================================ FILE: lib/artemis.dart ================================================ export 'client.dart'; export 'schema/graphql_query.dart'; export 'schema/graphql_response.dart'; ================================================ FILE: lib/builder.dart ================================================ import 'dart:async'; import 'package:artemis/generator/data/data.dart'; import 'package:artemis/transformer/add_typename_transformer.dart'; import 'package:build/build.dart'; import 'package:glob/glob.dart'; import 'package:gql/ast.dart'; import 'package:gql/language.dart'; import './generator.dart'; import './generator/print_helpers.dart'; import './schema/options.dart'; import 'generator/errors.dart'; /// [GraphQLQueryBuilder] instance, to be used by `build_runner`. GraphQLQueryBuilder graphQLQueryBuilder(BuilderOptions options) => GraphQLQueryBuilder(options); String _addGraphQLExtensionToPathIfNeeded(String path) { if (!path.endsWith('.graphql.dart')) { return path.replaceAll(RegExp(r'\.dart$'), '.graphql.dart'); } return path; } List _builderOptionsToExpectedOutputs(BuilderOptions builderOptions) { final schemaMapping = GeneratorOptions.fromJson(builderOptions.config).schemaMapping; if (schemaMapping.isEmpty) { throw MissingBuildConfigurationException('schema_mapping'); } if (schemaMapping.any((s) => s.output == null)) { throw MissingBuildConfigurationException('schema_mapping => output'); } return schemaMapping .map((s) { final outputWithoutLib = s.output!.replaceAll(RegExp(r'^lib/'), ''); return { outputWithoutLib, _addGraphQLExtensionToPathIfNeeded(outputWithoutLib), }.toList(); }) .expand((e) => e) .toList(); } /// Main Artemis builder. class GraphQLQueryBuilder implements Builder { /// Creates a builder from [BuilderOptions]. GraphQLQueryBuilder(BuilderOptions builderOptions) : options = GeneratorOptions.fromJson(builderOptions.config), expectedOutputs = _builderOptionsToExpectedOutputs(builderOptions); /// This generator options, gathered from `build.yaml` file. final GeneratorOptions options; /// List FragmentDefinitionNode in fragments_glob. // List fragmentsCommon = []; /// The generated output file. final List expectedOutputs; /// Callback fired when the generator processes a [QueryDefinition]. OnBuildQuery? onBuild; @override Map> get buildExtensions => { r'$lib$': expectedOutputs, }; /// read asset files Future> readGraphQlFiles( BuildStep buildStep, String schema, ) async { final schemaAssetStream = buildStep.findAssets(Glob(schema)); return await schemaAssetStream .asyncMap( (asset) async => parseString( await buildStep.readAsString(asset), url: asset.path, ), ) .toList(); } @override Future build(BuildStep buildStep) async { List fragmentsCommon = []; final fragmentsGlob = options.fragmentsGlob; if (fragmentsGlob != null) { final commonFragments = (await readGraphQlFiles(buildStep, fragmentsGlob)) .map((e) => e.definitions.whereType()) .expand((e) => e) .toList(); if (commonFragments.isEmpty) { throw MissingFilesException(fragmentsGlob); } fragmentsCommon.addAll(commonFragments); } for (final schemaMap in options.schemaMapping) { List schemaCommonFragments = [ ...fragmentsCommon, ]; final schemaFragmentsGlob = schemaMap.fragmentsGlob; if (schemaFragmentsGlob != null) { final schemaFragments = (await readGraphQlFiles(buildStep, schemaFragmentsGlob)) .map((e) => e.definitions.whereType()) .expand((e) => e) .toList(); if (schemaFragments.isEmpty) { throw MissingFilesException(schemaFragmentsGlob); } schemaCommonFragments.addAll(schemaFragments); } final queriesGlob = schemaMap.queriesGlob; final schema = schemaMap.schema; final output = schemaMap.output; if (schema == null) { throw MissingBuildConfigurationException('schema_map => schema'); } if (output == null) { throw MissingBuildConfigurationException('schema_map => output'); } // Loop through all files in glob if (queriesGlob == null) { throw MissingBuildConfigurationException('schema_map => queries_glob'); } else if (Glob(queriesGlob).matches(schema)) { throw QueryGlobsSchemaException(); } else if (Glob(queriesGlob).matches(output)) { throw QueryGlobsOutputException(); } final gqlSchema = await readGraphQlFiles(buildStep, schema); if (gqlSchema.isEmpty) { throw MissingFilesException(schema); } var gqlDocs = await readGraphQlFiles(buildStep, queriesGlob); if (gqlDocs.isEmpty) { throw MissingFilesException(queriesGlob); } if (schemaMap.appendTypeName) { gqlDocs = gqlDocs.map( (doc) { final transformed = transform(doc, [AppendTypename(schemaMap.typeNameField)]); // transform makes definitions growable: false so just recreate it again // as far as we need to add some elements there lately return DocumentNode( definitions: List.from(transformed.definitions), span: transformed.span, ); }, ).toList(); schemaCommonFragments = schemaCommonFragments .map((fragments) => transform( fragments, [AppendTypename(schemaMap.typeNameField)], )) .toList(); } final libDefinition = generateLibrary( _addGraphQLExtensionToPathIfNeeded(output), gqlDocs, options, schemaMap, schemaCommonFragments, gqlSchema.first, ); if (onBuild != null) { onBuild!(libDefinition); } final buffer = StringBuffer(); final outputFileId = AssetId( buildStep.inputId.package, _addGraphQLExtensionToPathIfNeeded(output), ); writeLibraryDefinitionToBuffer( buffer, options.ignoreForFile, libDefinition, ); await buildStep.writeAsString(outputFileId, buffer.toString()); if (!output.endsWith('.graphql.dart')) { final forwarderOutputFileId = AssetId(buildStep.inputId.package, output); await buildStep.writeAsString( forwarderOutputFileId, writeLibraryForwarder(libDefinition)); } } } } ================================================ FILE: lib/client.dart ================================================ import 'dart:async'; import 'package:gql_dedupe_link/gql_dedupe_link.dart'; import 'package:gql_exec/gql_exec.dart'; import 'package:gql_http_link/gql_http_link.dart'; import 'package:gql_link/gql_link.dart'; import 'package:http/http.dart' as http; import 'package:json_annotation/json_annotation.dart'; import './schema/graphql_query.dart'; import './schema/graphql_response.dart'; /// Used to execute a GraphQL query or mutation and return its typed response. /// /// A [Link] is used as the network interface. class ArtemisClient { HttpLink? _httpLink; final Link _link; /// Instantiate an [ArtemisClient]. /// /// [DedupeLink] and [HttpLink] are included. /// To use different [Link] create an [ArtemisClient] with [ArtemisClient.fromLink]. factory ArtemisClient( String graphQLEndpoint, { http.Client? httpClient, }) { final httpLink = HttpLink( graphQLEndpoint, httpClient: httpClient, ); return ArtemisClient.fromLink( Link.from([ DedupeLink(), httpLink, ]), ).._httpLink = httpLink; } /// Create an [ArtemisClient] from [Link]. ArtemisClient.fromLink(this._link); /// Executes a [GraphQLQuery], returning a typed response. Future> execute( GraphQLQuery query, { Context context = const Context(), }) async { final request = Request( operation: Operation( document: query.document, operationName: query.operationName, ), variables: query.getVariablesMap(), context: context, ); final response = await _link.request(request).first; return GraphQLResponse( data: response.data == null ? null : query.parse(response.data ?? {}), errors: response.errors, context: response.context, ); } /// Streams a [GraphQLQuery], returning a typed response stream. Stream> stream( GraphQLQuery query, { Context context = const Context(), }) { final request = Request( operation: Operation( document: query.document, operationName: query.operationName, ), variables: query.getVariablesMap(), context: context, ); return _link.request(request).map((response) => GraphQLResponse( data: response.data == null ? null : query.parse(response.data ?? {}), errors: response.errors, context: response.context, )); } /// Close the inline [http.Client]. /// /// Keep in mind this will not close clients whose Artemis client /// was instantiated from [ArtemisClient.fromLink]. If you're using /// this constructor, you need to close your own links. void dispose() { _httpLink?.dispose(); } } ================================================ FILE: lib/generator/data/class_definition.dart ================================================ import 'package:artemis/generator/data/class_property.dart'; import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data/fragment_class_definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:recase/recase.dart'; /// Define a Dart class parsed from GraphQL type. class ClassDefinition extends Definition with DataPrinter { /// The properties (fields) of the class. final Iterable properties; /// The type this class extends from, or [null]. final Name? extension; /// The types this class implements. final Iterable implementations; /// The types this class mixins. final Iterable mixins; /// The types possibilities (GraphQL type -> class name) the class /// implements, if it's part of an union type or interface. final Map factoryPossibilities; /// The field name used to resolve this class type. final ClassPropertyName typeNameField; /// Whether this is an input object or not. final bool isInput; /// Instantiate a class definition. ClassDefinition({ required Name name, this.properties = const [], this.extension, this.implementations = const [], this.mixins = const [], this.factoryPossibilities = const {}, ClassPropertyName? typeNameField, this.isInput = false, }) : typeNameField = typeNameField ?? ClassPropertyName(name: '__typename'), super(name: name); @override Map get namedProps => { 'name': name, 'properties': properties, 'extension': extension, 'implementations': implementations, 'mixins': mixins, 'factoryPossibilities': factoryPossibilities, 'typeNameField': typeNameField, 'isInput': isInput, }; } /// Class name. class ClassName extends Name with DataPrinter { /// Instantiate a class name definition. ClassName({required String name}) : assert(hasValue(name)), super(name: name); /// Generate class name from hierarchical path factory ClassName.fromPath({required List path}) { return ClassName(name: path.map((e) => e.dartTypeSafe).join(r'$_')); } @override Map get namedProps => { 'name': name, }; @override String normalize(String name) { return ReCase(super.normalize(name)).pascalCase; } } ================================================ FILE: lib/generator/data/class_property.dart ================================================ import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:recase/recase.dart'; /// Define a property (field) from a class. class ClassProperty extends Definition with DataPrinter { @override final ClassPropertyName name; /// The property type. final TypeName type; /// Some other custom annotation. final List annotations; /// Whether this parameter corresponds to the __resolveType (or equivalent) final bool isResolveType; /// Instantiate a property (field) from a class. ClassProperty({ required this.name, required this.type, this.annotations = const [], this.isResolveType = false, }) : assert(hasValue(type) && hasValue(name)), super(name: name); /// If property is an override from super class. bool get isOverride => annotations.contains('override'); /// Creates a copy of [ClassProperty] without modifying the original. ClassProperty copyWith({ TypeName? type, ClassPropertyName? name, List? annotations, bool? isResolveType, }) => ClassProperty( type: type ?? this.type, name: name ?? this.name, annotations: annotations ?? this.annotations, isResolveType: isResolveType ?? this.isResolveType, ); @override Map get namedProps => { 'type': type, 'name': name, 'annotations': annotations, 'isResolveType': isResolveType, }; } /// Class property name class ClassPropertyName extends Name with DataPrinter { /// Instantiate a class property name definition. const ClassPropertyName({required String name}) : super(name: name); @override String normalize(String name) { final normalized = super.normalize(name); final suffix = RegExp(r'.*(_+)$').firstMatch(normalized)?.group(1) ?? ''; return ReCase(super.normalize(name)).camelCase + suffix; } @override Map get namedProps => { 'name': name, }; } const _camelCaseTypes = {'bool', 'double', 'int'}; /// Type name class TypeName extends Name with DataPrinter { /// Instantiate a type name definition. TypeName({ required String name, this.isNonNull = false, }) : super(name: name); /// If this type is non-null final bool isNonNull; @override Map get namedProps => { 'name': name, if (isNonNull) 'isNonNull': true, }; @override List get props => [name, isNonNull]; @override String normalize(String name) { final normalized = super.normalize(name); if (_camelCaseTypes.contains(normalized)) { return '$normalized${isNonNull ? '' : '?'}'; } return '${ReCase(normalized).pascalCase}${isNonNull ? '' : '?'}'; } } /// Type name class DartTypeName extends TypeName with DataPrinter { /// Instantiate a type name definition. DartTypeName({ required String name, bool isNonNull = false, }) : super(name: name, isNonNull: isNonNull); @override String normalize(String name) => '$name${isNonNull ? '' : '?'}'; } /// Type name class ListOfTypeName extends TypeName with DataPrinter { /// Instantiate a type name definition. ListOfTypeName({ required this.typeName, this.isNonNull = true, }) : super(name: typeName.name, isNonNull: isNonNull); /// Internal type name final TypeName typeName; /// If this list type is non-null @override final bool isNonNull; @override Map get namedProps => { 'typeName': typeName, 'isNonNull': isNonNull, }; @override String normalize(String? name) => 'List<${typeName.namePrintable}>${isNonNull ? '' : '?'}'; } ================================================ FILE: lib/generator/data/data.dart ================================================ export 'class_definition.dart'; export 'class_property.dart'; export 'enum_definition.dart'; export 'fragment_class_definition.dart'; export 'library_definition.dart'; export 'definition.dart'; export 'query_definition.dart'; export 'query_input.dart'; ================================================ FILE: lib/generator/data/definition.dart ================================================ import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:equatable/equatable.dart'; /// Abstract definition of an entity. abstract class Definition extends Equatable with DataPrinter { /// The definition name. final Name name; /// Instantiate a definition. Definition({required this.name}); } /// Abstract name of an entity. abstract class Name extends Equatable with DataPrinter { /// Raw name string final String name; /// Instantiate a name. const Name({required this.name}); /// Name suitable for code printing String get namePrintable => normalize(name); /// type name safe to use for dart String get dartTypeSafe => namePrintable.replaceAll(RegExp(r'[<>?]'), ''); /// type name safe to use for dart String get parserSafe { final reGeneric = RegExp(r'<([\S]*)>'); final reNull = RegExp(r'[?]'); return [ namePrintable.replaceAll(reGeneric, '').replaceAll(reNull, 'Nullable'), reGeneric .allMatches(namePrintable) .map((e) => e.group(1)) .join('') .replaceAll(reNull, 'Nullable') ].join(''); } /// Name normalization function String normalize(String name) => normalizeName(name); } ================================================ FILE: lib/generator/data/enum_definition.dart ================================================ import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:recase/recase.dart'; /// Define a Dart enum parsed from GraphQL schema. class EnumDefinition extends Definition with DataPrinter { @override final EnumName name; /// The possible values of this enum. final Iterable values; /// Instantiate an enum definition. EnumDefinition({ required this.name, required this.values, }) : assert(hasValue(name) && hasValue(values)), super(name: name); @override Map get namedProps => { 'name': name, 'values': values, }; } /// Enum name class EnumName extends Name with DataPrinter { /// Instantiate a enum name definition. EnumName({required String name}) : super(name: name); @override String normalize(String name) { return ReCase(super.normalize(name)).pascalCase; } @override Map get namedProps => { 'name': name, }; } ================================================ FILE: lib/generator/data/enum_value_definition.dart ================================================ import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:recase/recase.dart'; /// Enum value class EnumValueDefinition extends Definition with DataPrinter { @override final EnumValueName name; /// Some other custom annotation. final List annotations; /// Instantiate an enum value EnumValueDefinition({ required this.name, this.annotations = const [], }) : super(name: name); @override Map get namedProps => { 'name': name, 'annotations': annotations, }; } /// Enum value name class EnumValueName extends Name with DataPrinter { /// Instantiate a enum value name definition. EnumValueName({required String name}) : super(name: name); @override Map get namedProps => { 'name': name, }; @override String normalize(String name) { return ReCase(super.normalize(name)).camelCase; } } ================================================ FILE: lib/generator/data/fragment_class_definition.dart ================================================ import 'package:artemis/generator/data/class_property.dart'; import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:recase/recase.dart'; /// Define a Dart class parsed from GraphQL fragment. class FragmentClassDefinition extends Definition with DataPrinter { @override final FragmentName name; /// The properties (fields) of the class. final Iterable properties; /// Instantiate a fragment class definition. FragmentClassDefinition({ required this.name, required this.properties, }) : assert(hasValue(name) && hasValue(properties)), super(name: name); @override Map get namedProps => { 'name': name, 'properties': properties, }; } /// Fragment name class FragmentName extends Name with DataPrinter { /// Instantiate a fragment name definition. FragmentName({required String name}) : super(name: name); /// Generate class name from hierarchical path factory FragmentName.fromPath({required List path}) { return FragmentName(name: path.map((e) => e!.dartTypeSafe).join(r'$_')); } @override Map get namedProps => { 'name': name, }; @override String normalize(String name) { final normalizedName = ReCase(super.normalize(name)).pascalCase; if (normalizedName.endsWith('Mixin')) { return name; } return '${normalizedName}Mixin'; } } ================================================ FILE: lib/generator/data/library_definition.dart ================================================ import 'package:artemis/generator/data/query_definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:equatable/equatable.dart'; /// Callback fired when the generator processes a [LibraryDefinition]. typedef OnBuildQuery = void Function(LibraryDefinition definition); /// Define a whole library file, the output of a single [SchemaMap] code /// generation. class LibraryDefinition extends Equatable with DataPrinter { /// The output file basename. final String basename; /// A list of queries. final Iterable queries; /// Any other custom package imports, defined in `build.yaml`. final Iterable customImports; /// Instantiate a library definition. LibraryDefinition({ required this.basename, this.queries = const [], this.customImports = const [], }) : assert(hasValue(basename)); @override Map get namedProps => { 'basename': basename, 'queries': queries, 'customImports': customImports, }; } ================================================ FILE: lib/generator/data/nullable.dart ================================================ /// Allows to reset values back to null in `copyWith` pattern class Nullable { final T _value; /// Sets desired value Nullable(this._value); /// Gets the real value T get value { return _value; } } ================================================ FILE: lib/generator/data/query_definition.dart ================================================ import 'package:artemis/generator/data/class_definition.dart'; import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data/query_input.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:gql/ast.dart'; import 'package:recase/recase.dart'; /// Define a GraphQL query and its dependencies. class QueryDefinition extends Definition with DataPrinter { /// graphql operation name for helper classes final String operationName; /// The AST representation of GraphQL document. final DocumentNode document; /// A list of classes related to this query. final Iterable classes; /// A list of inputs related to this query. final Iterable inputs; /// If instances of [GraphQLQuery] should be generated. final bool generateHelpers; /// If query documents and operation names should be generated final bool generateQueries; /// The suffix of generated [GraphQLQuery] classes. final String suffix; /// Instantiate a query definition. QueryDefinition({ required Name name, required this.operationName, this.document = const DocumentNode(), this.classes = const [], this.inputs = const [], this.generateHelpers = false, this.generateQueries = false, this.suffix = 'Query', }) : assert(hasValue(operationName)), super(name: name); /// class name for helper classes String? get className => ClassName(name: operationName).namePrintable; /// name for document constant String get documentName => '$className${suffix}Document'; /// name for document operation name constant String get documentOperationName => '$className${suffix}DocumentOperationName'; @override Map get namedProps => { 'name': name, 'operationName': operationName, 'classes': classes, 'inputs': inputs, 'generateHelpers': generateHelpers, 'suffix': suffix, }; } /// Query name class QueryName extends Name with DataPrinter { /// Instantiate a query name definition. QueryName({required String name}) : assert(hasValue(name)), super(name: name); /// Generate class name from hierarchical path factory QueryName.fromPath({required List path}) { return QueryName(name: path.map((e) => e!.dartTypeSafe).join(r'$_')); } @override Map get namedProps => { 'name': name, }; @override String normalize(String name) { return ReCase(super.normalize(name)).pascalCase; } } ================================================ FILE: lib/generator/data/query_input.dart ================================================ import 'package:artemis/generator/data/class_property.dart'; import 'package:artemis/generator/data/definition.dart'; import 'package:artemis/generator/data_printer.dart'; import 'package:artemis/generator/helpers.dart'; /// Define a query/mutation input parameter. class QueryInput extends Definition with DataPrinter { @override final QueryInputName name; /// The input type. final TypeName type; /// Some other custom annotation. final List annotations; /// Instantiate an input parameter. QueryInput({ required this.type, this.annotations = const [], required this.name, }) : assert(hasValue(type) && hasValue(name)), super(name: name); @override Map get namedProps => { 'type': type, 'name': name, 'annotations': annotations, }; } /// class QueryInputName extends Name { /// QueryInputName({required String name}) : super(name: name); @override Map get namedProps => { 'name': name, }; } ================================================ FILE: lib/generator/data_printer.dart ================================================ import 'package:equatable/equatable.dart'; import 'helpers.dart'; String? _formatPrint(Object? obj) { if (obj is Map) { return '{${obj.entries.map((e) => '${_formatPrint(e.key)}: ${_formatPrint(e.value)}').join(', ')}}'; } else if (obj is Iterable) { return '[${obj.map(_formatPrint).join(', ')}]'; } else if (obj is String) { final bt = obj.contains('\''); return 'r\'${bt ? '\'\'' : ''}$obj${bt ? '\'\'' : ''}\''; } final str = obj.toString(); if (str.startsWith('Instance of')) { return null; } return str; } /// Data printer mixin mixin DataPrinter on Equatable { /// Map get namedProps; @override List get props => namedProps.values.toList(); @override String toString() { final params = namedProps.entries .map((e) => hasValue(e.value) ? '${e.key}:${_formatPrint(e.value)}' : null) .where((o) => o != null) .join(', '); return '$runtimeType($params)'; } } ================================================ FILE: lib/generator/ephemeral_data.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/nullable.dart'; import 'package:artemis/visitor/type_definition_node_visitor.dart'; import 'package:gql/ast.dart'; import '../schema/options.dart'; /// Returns the full class name with joined path. List createPathName(List path, NamingScheme? namingScheme, [Name? currentClassName, Name? currentFieldName, Name? alias]) { final fieldName = alias ?? currentFieldName; final className = alias ?? currentClassName; List fullPath; switch (namingScheme) { case NamingScheme.simple: fullPath = className == null // fix for https://github.com/comigor/artemis/issues/226 ? (path.length == 2 ? path : [path.last]) : [className]; break; case NamingScheme.pathedWithFields: fullPath = [...path, fieldName]; break; case NamingScheme.pathedWithTypes: default: fullPath = [...path, className]; break; } return fullPath.whereType().toList(); } /// Holds context between [_GeneratorVisitor] iterations. class Context { /// Instantiates context for [_GeneratorVisitor] iterations. Context({ required this.schema, required this.typeDefinitionNodeVisitor, required this.options, required this.schemaMap, required this.path, required this.currentType, required this.currentFieldName, required this.currentClassName, this.alias, this.ofUnion, required this.generatedClasses, required this.inputsClasses, required this.fragments, this.usedEnums = const {}, this.usedInputObjects = const {}, this.align = 0, this.log = true, }); /// The [DocumentNode] parsed from `build.yaml` configuration. final DocumentNode schema; /// The [DocumentNode] parsed from `build.yaml` configuration. final TypeDefinitionNodeVisitor typeDefinitionNodeVisitor; /// Other options parsed from `build.yaml` configuration. final GeneratorOptions options; /// The [SchemaMap] being used on this iteration. final SchemaMap schemaMap; /// The path of data we're currently processing. final List path; /// The [TypeDefinitionNode] we're currently processing. final TypeDefinitionNode? currentType; /// The name of the class we're currently processing. final Name? currentClassName; /// The name of the field we're currently processing. final Name? currentFieldName; /// If part of an union type, which [TypeDefinitionNode] it represents. final TypeDefinitionNode? ofUnion; /// A string to replace the current class name. final Name? alias; /// The current generated definition classes of this visitor. final List generatedClasses; /// The current generated input classes of this visitor. final List inputsClasses; /// The current fragments considered in this visitor. final List fragments; /// The indentation used to debugging purposes. final int align; /// If debug log should be printed. final bool log; /// A list of used enums (to filtered on generation). final Set usedEnums; /// A list of used input objects (to filtered on generation). final Set usedInputObjects; Name? _stringForNaming(Name? withFieldNames, Name? withClassNames) => schemaMap.namingScheme == NamingScheme.pathedWithFields ? withFieldNames : withClassNames; /// Returns the full class name List fullPathName() => createPathName( path, schemaMap.namingScheme, currentClassName, currentFieldName, alias); /// Returns a copy of this context, on the same path, but with a new type. Context nextTypeWithSamePath({ required TypeDefinitionNode nextType, required Name? nextFieldName, required Name? nextClassName, Nullable? ofUnion, Name? alias, List? generatedClasses, List? inputsClasses, List? fragments, }) => Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: path, currentType: nextType, currentFieldName: nextFieldName, currentClassName: nextClassName, ofUnion: ofUnion == null ? this.ofUnion : ofUnion.value, generatedClasses: generatedClasses ?? this.generatedClasses, inputsClasses: inputsClasses ?? this.inputsClasses, fragments: fragments ?? this.fragments, align: align, usedEnums: usedEnums, usedInputObjects: usedInputObjects, ); /// Returns a copy of this context, with a new type on a new path. Context next({ required TypeDefinitionNode nextType, Name? nextFieldName, Name? nextClassName, Name? alias, Nullable? ofUnion, List? generatedClasses, List? inputsClasses, List? fragments, }) { assert(alias != null || (nextFieldName != null && nextClassName != null)); return Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: path .followedBy([ _stringForNaming( alias ?? nextFieldName, alias ?? nextClassName, ) ].whereType()) .toList(), currentType: nextType, currentFieldName: nextFieldName, currentClassName: nextClassName, ofUnion: ofUnion == null ? this.ofUnion : ofUnion.value, generatedClasses: generatedClasses ?? this.generatedClasses, inputsClasses: inputsClasses ?? this.inputsClasses, fragments: fragments ?? this.fragments, align: align + 1, usedEnums: usedEnums, usedInputObjects: usedInputObjects, ); } /// Returns a copy of this context, with the same type and path. Context withAlias({ Name? nextFieldName, Name? nextClassName, Name? alias, }) => Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: path, currentType: currentType, currentFieldName: nextFieldName, currentClassName: nextClassName, ofUnion: ofUnion, alias: alias, generatedClasses: generatedClasses, inputsClasses: inputsClasses, fragments: fragments, align: align, usedEnums: usedEnums, usedInputObjects: usedInputObjects, ); /// Returns a copy of this context, with the same type, but on a new path. Context sameTypeWithNextPath({ Name? nextFieldName, Name? nextClassName, Name? alias, Nullable? ofUnion, List? generatedClasses, List? inputsClasses, List? fragments, bool? log, }) { assert(alias != null || (nextFieldName != null && nextClassName != null)); return Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: path .followedBy([ _stringForNaming( alias ?? nextFieldName, alias ?? nextClassName, ), ].whereType()) .toList(), currentType: currentType, currentFieldName: nextFieldName ?? currentFieldName, currentClassName: nextClassName ?? currentClassName, ofUnion: ofUnion == null ? this.ofUnion : ofUnion.value, alias: alias ?? this.alias, generatedClasses: generatedClasses ?? this.generatedClasses, inputsClasses: inputsClasses ?? this.inputsClasses, fragments: fragments ?? this.fragments, align: align + 1, usedEnums: usedEnums, usedInputObjects: usedInputObjects, log: log ?? this.log, ); } /// Returns a copy of this context, rolling back a item on path. Context rollbackPath() { return Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: [...path]..removeLast(), currentType: currentType, currentFieldName: currentFieldName, currentClassName: currentClassName, ofUnion: ofUnion, alias: alias, generatedClasses: generatedClasses, inputsClasses: inputsClasses, fragments: fragments, align: align - 1, usedEnums: usedEnums, usedInputObjects: usedInputObjects, ); } /// Returns a copy of this context, with the same type, but on the first path. Context sameTypeWithNoPath({ Name? alias, Nullable? ofUnion, List? generatedClasses, List? inputsClasses, List? fragments, }) => Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: [], currentType: currentType, currentFieldName: currentFieldName, currentClassName: currentClassName, ofUnion: ofUnion == null ? this.ofUnion : ofUnion.value, alias: alias ?? this.alias, generatedClasses: generatedClasses ?? this.generatedClasses, inputsClasses: inputsClasses ?? this.inputsClasses, fragments: fragments ?? this.fragments, align: align, usedEnums: usedEnums, usedInputObjects: usedInputObjects, ); /// Returns a copy of this context, with next type, but on the first path. Context nextTypeWithNoPath({ required TypeDefinitionNode nextType, required Name nextFieldName, required Name nextClassName, Nullable? ofUnion, Name? alias, List? generatedClasses, List? inputsClasses, List? fragments, }) => Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: [], currentType: nextType, currentFieldName: nextFieldName, currentClassName: nextClassName, ofUnion: ofUnion == null ? this.ofUnion : ofUnion.value, alias: alias ?? this.alias, generatedClasses: generatedClasses ?? this.generatedClasses, inputsClasses: inputsClasses ?? this.inputsClasses, fragments: fragments ?? this.fragments, align: 0, usedEnums: usedEnums, usedInputObjects: usedInputObjects, ); } ================================================ FILE: lib/generator/errors.dart ================================================ import 'package:artemis/generator/data/data.dart'; /// Define an exception thrown when duplicated classes names were generated. class DuplicatedClassesException implements Exception { /// Define an exception thrown when duplicated classes names were generated. const DuplicatedClassesException(this.a, this.b); /// First duplicated class. final Definition a; /// Second duplicated class. final Definition b; @override String toString() => '''Two classes were generated with the same name `${a.name}` but with different selection set. Class A ${a.toString()} Class B ${b.toString()} '''; } /// Define an exception thrown when `queries_glob` configuration contains the /// `schema` (Artemis would try to generate the schema as a query). class QueryGlobsSchemaException implements Exception { /// Define an exception thrown when `queries_glob` configuration contains the /// `schema` (Artemis would try to generate the schema as a query). const QueryGlobsSchemaException(); @override String toString() => '''One of your `queries_glob` configuration contains the path to the `schema` file! Change `schema` file location and try again. '''; } /// Define an exception thrown when `queries_glob` configuration contains `output`. class QueryGlobsOutputException implements Exception { /// Define an exception thrown when `queries_glob` configuration contains `output`. const QueryGlobsOutputException(); @override String toString() => '''One of your `queries_glob` configuration contains the path to the `output` file! Change `schema` or `output` location and try again. '''; } /// Define an exception thrown when Artemis does not find asset files class MissingFilesException implements Exception { /// glob pattern which was used final String globPattern; /// Define an exception thrown when Artemis does not find asset files MissingFilesException(this.globPattern); @override String toString() { return 'Missing files for $globPattern'; } } /// Define an exception thrown when Artemis does not find required config params class MissingBuildConfigurationException implements Exception { /// missing config option name final String name; /// Define an exception thrown when Artemis does not find required config params MissingBuildConfigurationException(this.name); @override String toString() => 'Missing `$name` configuration option. Cehck `build.yaml` configuration'; } /// Define an exception thrown when Artemis find a scalar on schema but it's /// not configured on `build.yaml`. class MissingScalarConfigurationException implements Exception { /// Define an exception thrown when Artemis find a scalar on schema but it's /// not configured on `build.yaml`. const MissingScalarConfigurationException(this.scalarName); /// The misconfigured scalar name. final String scalarName; @override String toString() => '''Your `schema` file contains "$scalarName" scalar, but this scalar is not configured on `build.yaml`! Please configure it, following the README on `scalar_mapping`. '''; } /// Thrown when Artemis can't find the default (or configured) root object type /// on schema. class MissingRootTypeException implements Exception { /// Thrown when Artemis can't find the default (or configured) root object /// type on schema. const MissingRootTypeException(this.rootTypeName); /// The missing root type name. final String rootTypeName; @override String toString() => '''Can't find the "$rootTypeName" root type. Make sure your schema file contains it. '''; } /// Thrown when Artemis can't find the requested fragment on schema. class MissingFragmentException implements Exception { /// Thrown when Artemis can't find the requested fragment on schema. const MissingFragmentException(this.fragmentName, this.className); /// The missing fragment name. final String fragmentName; /// The class name in which the fragment is used. final String className; @override String toString() => '''Can't find the "$fragmentName" in "$className". Make sure files inside `fragments_glob` or the query file contains it. '''; } ================================================ FILE: lib/generator/graphql_helpers.dart ================================================ import 'package:artemis/generator/errors.dart'; import 'package:artemis/visitor/type_definition_node_visitor.dart'; import 'package:gql/ast.dart'; import '../generator/data/data.dart'; import '../schema/options.dart'; /// Get a full [TypeDefinitionNode] from a type node. TypeDefinitionNode getTypeByName( TypeDefinitionNodeVisitor typeDefinitionNodeVisitor, TypeNode typeNode, ) { late NamedTypeNode namedNode; if (typeNode is ListTypeNode) { return getTypeByName(typeDefinitionNodeVisitor, typeNode.type); } if (typeNode is NamedTypeNode) { namedNode = typeNode; } final type = typeDefinitionNodeVisitor.getByName(namedNode.name.value); if (type == null) { throw Exception('''Type ${namedNode.name.value} was not found in schema. Make sure your query is correct and your schema is updated.'''); } return type; } /// Build a string representing a Dart type, given a GraphQL type. TypeName buildTypeName( Node node, GeneratorOptions options, { bool dartType = true, Name? replaceLeafWith, required TypeDefinitionNodeVisitor typeDefinitionNodeVisitor, }) { if (node is NamedTypeNode) { final type = typeDefinitionNodeVisitor.getByName(node.name.value); if (type != null) { if (type is ScalarTypeDefinitionNode) { final scalar = getSingleScalarMap(options, node.name.value); final dartTypeValue = scalar?.dartType; final graphQLTypeValue = scalar?.graphQLType; if (dartType && dartTypeValue != null && dartTypeValue.name != null) { return DartTypeName( name: dartTypeValue.name!, isNonNull: node.isNonNull, ); } else if (graphQLTypeValue != null) { return TypeName( name: graphQLTypeValue, isNonNull: node.isNonNull, ); } } if (type is EnumTypeDefinitionNode || type is InputObjectTypeDefinitionNode) { return TypeName(name: type.name.value, isNonNull: node.isNonNull); } if (replaceLeafWith != null) { return TypeName(name: replaceLeafWith.name, isNonNull: node.isNonNull); } else { return TypeName(name: type.name.value, isNonNull: node.isNonNull); } } return TypeName(name: node.name.value, isNonNull: node.isNonNull); } if (node is ListTypeNode) { final typeName = buildTypeName( node.type, options, dartType: dartType, replaceLeafWith: replaceLeafWith, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, ); return ListOfTypeName( typeName: typeName, isNonNull: node.isNonNull, ); } throw Exception('Unable to build type name'); } Map _defaultScalarMapping = { 'Boolean': ScalarMap(graphQLType: 'Boolean', dartType: const DartType(name: 'bool')), 'Float': ScalarMap(graphQLType: 'Float', dartType: const DartType(name: 'double')), 'ID': ScalarMap(graphQLType: 'ID', dartType: const DartType(name: 'String')), 'Int': ScalarMap(graphQLType: 'Int', dartType: const DartType(name: 'int')), 'String': ScalarMap( graphQLType: 'String', dartType: const DartType(name: 'String')), }; /// Retrieve a scalar mapping of a type. ScalarMap? getSingleScalarMap(GeneratorOptions options, String type, {bool throwOnNotFound = true}) { final scalarMap = options.scalarMapping.followedBy(_defaultScalarMapping.values).firstWhere( (m) => m!.graphQLType == type, orElse: () => null, ); if (throwOnNotFound && scalarMap == null) { throw MissingScalarConfigurationException(type); } return scalarMap; } /// Retrieve imports of a scalar map. Iterable importsOfScalar(GeneratorOptions options, String type) { final scalarMapping = options.scalarMapping.firstWhere( (m) => m!.graphQLType == type, orElse: () => null, ); final customParserImport = scalarMapping?.customParserImport; final result = List.from(scalarMapping?.dartType?.imports ?? []); if (customParserImport != null) { result.add(customParserImport); } return result; } ================================================ FILE: lib/generator/helpers.dart ================================================ import 'package:artemis/generator/ephemeral_data.dart'; import 'package:build/build.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:gql/ast.dart'; typedef IterableFunction = U Function(T i); typedef MergeableFunction = T Function(T oldT, T newT); /// a list of dart lang keywords List dartKeywords = const [ 'abstract', 'else', 'import', 'super', 'as', 'enum', 'in', 'switch', 'assert', 'export', 'interface', 'sync', 'async', 'extends', 'is', 'this', 'await', 'extension', 'library', 'throw', 'break', 'external', 'mixin', 'true', 'case', 'factory', 'new', 'try', 'catch', 'false', 'null', 'typedef', 'class', 'final', 'on', 'var', 'const', 'finally', 'operator', 'void', 'continue', 'for', 'part', 'while', 'covariant', 'Function', 'rethrow', 'with', 'default', 'get', 'return', 'yield', 'deferred', 'hide', 'set', 'do', 'if', 'show', 'dynamic', 'implements', 'static', ]; Iterable _removeDuplicatedBy( Iterable list, IterableFunction fn) { final values = {}; return list.where((i) { final value = fn(i); return values.update(value, (_) => false, ifAbsent: () => true); }).toList(); } /// normalizes name /// _variable => $variable /// __typename => $$typename /// new -> kw$new String normalizeName(String name) { final regExp = RegExp(r'^(_+)([\w$]*)$'); var matches = regExp.allMatches(name); if (matches.isNotEmpty) { var match = matches.elementAt(0); var fieldName = match.group(2)!; return fieldName.padLeft(name.length, r'$'); } if (dartKeywords.contains(name.toLowerCase())) { return 'kw\$$name'; } return name; } Iterable _mergeDuplicatesBy( Iterable list, IterableFunction fn, MergeableFunction mergeFn, ) { final values = {}; for (final i in list) { final value = fn(i); values.update(value, (oldI) => mergeFn(oldI, i), ifAbsent: () => i); } return values.values.toList(); } /// Merge multiple values from an iterable given a predicate without modifying /// the original iterable. extension ExtensionsOnIterable on Iterable { /// Merge multiple values from an iterable given a predicate without modifying /// the original iterable. Iterable mergeDuplicatesBy( IterableFunction fn, MergeableFunction mergeFn) => _mergeDuplicatesBy(this, fn, mergeFn); /// Remove duplicated values from an iterable given a predicate without /// modifying the original iterable. Iterable removeDuplicatedBy(IterableFunction fn) => _removeDuplicatedBy(this, fn); } /// Check if [obj] has value (isn't null or empty). bool hasValue(Object? obj) { if (obj is Iterable) { return obj.isNotEmpty; } return obj != null && obj.toString().isNotEmpty; } /// Proceeds deprecated annotation List proceedDeprecated( List? directives, ) { final annotations = []; final deprecatedDirective = directives?.firstWhereOrNull( (directive) => directive.name.value == 'deprecated', ); if (deprecatedDirective != null) { final reasonValueNode = deprecatedDirective.arguments .firstWhereOrNull((argument) => argument.name.value == 'reason') ?.value; final reason = reasonValueNode is StringValueNode ? reasonValueNode.value.replaceAll("'", "\\'").replaceAll(r'$', r'\$') : 'No longer supported'; annotations.add("Deprecated('$reason')"); } return annotations; } /// Logger function void logFn(Context context, int align, Object logObject) { if (!context.log) return; log.fine('${List.filled(align, '| ').join()}${logObject.toString()}'); } ================================================ FILE: lib/generator/print_helpers.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:artemis/generator/errors.dart'; import 'package:code_builder/code_builder.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:dart_style/dart_style.dart'; // ignore: implementation_imports import 'package:gql_code_builder/src/ast.dart' as dart; import 'package:recase/recase.dart'; import '../generator/helpers.dart'; /// Generates a [Spec] of a single enum definition. Spec enumDefinitionToSpec(EnumDefinition definition) => CodeExpression(Code('''enum ${definition.name.namePrintable} { ${definition.values.removeDuplicatedBy((i) => i).map(_enumValueToSpec).join()} }''')); String _enumValueToSpec(EnumValueDefinition value) { final annotations = value.annotations .map((annotation) => '@$annotation') .followedBy(['@JsonValue(\'${value.name.name}\')']).join(' '); return '$annotations${value.name.namePrintable}, '; } String _fromJsonBody(ClassDefinition definition) { final buffer = StringBuffer(); buffer.writeln( '''switch (json['${definition.typeNameField.name}'].toString()) {'''); for (final p in definition.factoryPossibilities.entries) { buffer.writeln(''' case r'${p.key}': return ${p.value.namePrintable}.fromJson(json);'''); } buffer.writeln(''' default: } return _\$${definition.name.namePrintable}FromJson(json);'''); return buffer.toString(); } String _toJsonBody(ClassDefinition definition) { final buffer = StringBuffer(); final typeName = definition.typeNameField.namePrintable; buffer.writeln('''switch ($typeName) {'''); for (final p in definition.factoryPossibilities.entries) { buffer.writeln(''' case r'${p.key}': return (this as ${p.value.namePrintable}).toJson();'''); } buffer.writeln(''' default: } return _\$${definition.name.namePrintable}ToJson(this);'''); return buffer.toString(); } Method _propsMethod(Iterable body) { return Method((m) => m ..type = MethodType.getter ..returns = refer('List') ..annotations.add(CodeExpression(Code('override'))) ..name = 'props' ..lambda = true ..body = Code('[${body.mergeDuplicatesBy((i) => i, (a, b) => a).join(', ')}]')); } /// Generates a [Spec] of a single class definition. Spec classDefinitionToSpec( ClassDefinition definition, Iterable fragments, Iterable classes) { final fromJson = definition.factoryPossibilities.isNotEmpty ? Constructor( (b) => b ..factory = true ..name = 'fromJson' ..requiredParameters.add(Parameter( (p) => p ..type = refer('Map') ..name = 'json', )) ..body = Code(_fromJsonBody(definition)), ) : Constructor( (b) => b ..factory = true ..name = 'fromJson' ..lambda = true ..requiredParameters.add(Parameter( (p) => p ..type = refer('Map') ..name = 'json', )) ..body = Code('_\$${definition.name.namePrintable}FromJson(json)'), ); final toJson = definition.factoryPossibilities.isNotEmpty ? Method( (m) => m ..name = 'toJson' ..annotations.add(CodeExpression(Code('override'))) ..returns = refer('Map') ..body = Code(_toJsonBody(definition)), ) : Method( (m) => m ..name = 'toJson' ..lambda = true ..annotations.add(CodeExpression(Code('override'))) ..returns = refer('Map') ..body = Code('_\$${definition.name.namePrintable}ToJson(this)'), ); final props = definition.mixins .map((i) { return fragments .firstWhere((f) { return f.name == i; }, orElse: () { throw MissingFragmentException( i.namePrintable, definition.name.namePrintable); }) .properties .map((p) => p.name.namePrintable); }) .expand((i) => i) .followedBy(definition.properties.map((p) => p.name.namePrintable)); final extendedClass = classes.firstWhereOrNull((e) => e.name == definition.extension); return Class( (b) => b ..annotations .add(CodeExpression(Code('JsonSerializable(explicitToJson: true)'))) ..name = definition.name.namePrintable ..mixins.add(refer('EquatableMixin')) ..mixins.addAll(definition.mixins.map((i) => refer(i.namePrintable))) ..methods.add(_propsMethod(props)) ..extend = definition.extension != null ? refer(definition.extension!.namePrintable) : refer('JsonSerializable') ..implements.addAll(definition.implementations.map((i) => refer(i))) ..constructors.add(Constructor((b) { if (definition.isInput) { b.optionalParameters.addAll(definition.properties .where( (property) => !property.isOverride && !property.isResolveType) .map( (property) => Parameter( (p) { p ..name = property.name.namePrintable ..named = true ..toThis = true ..required = property.type.isNonNull; }, ), )); } })) ..constructors.add(fromJson) ..methods.add(toJson) ..fields.addAll(definition.properties.map((p) { if (extendedClass != null && extendedClass.properties.any((e) => e == p)) { // if class has the same prop as in extension p.annotations.add('override'); } final field = Field((f) { f ..name = p.name.namePrintable // TODO: remove this workaround when code_builder includes late field modifier: // https://github.com/dart-lang/code_builder/pull/310 ..type = refer( '${p.type.isNonNull ? 'late ' : ''} ${p.type.namePrintable}') ..annotations.addAll( p.annotations.map((e) => CodeExpression(Code(e))), ); if (p.type.isNonNull) { // TODO: apply this fix when code_builder includes late field modifier: // https://github.com/dart-lang/code_builder/pull/310 // f.modifier = FieldModifier.late$; } }); return field; })), ); } /// Generates a [Spec] of a single fragment class definition. Spec fragmentClassDefinitionToSpec(FragmentClassDefinition definition) { final fields = definition.properties.map((p) { final lines = []; lines.addAll(p.annotations.map((e) => '@$e')); lines.add( '${p.type.isNonNull ? 'late ' : ''}${p.type.namePrintable} ${p.name.namePrintable};'); return lines.join('\n'); }); return CodeExpression(Code('''mixin ${definition.name.namePrintable} { ${fields.join('\n')} }''')); } /// Generates a [Spec] of a mutation argument class. Spec generateArgumentClassSpec(QueryDefinition definition) { return Class( (b) => b ..annotations .add(CodeExpression(Code('JsonSerializable(explicitToJson: true)'))) ..name = '${definition.className}Arguments' ..extend = refer('JsonSerializable') ..mixins.add(refer('EquatableMixin')) ..methods.add(_propsMethod( definition.inputs.map((input) => input.name.namePrintable))) ..constructors.add(Constructor( (b) => b ..optionalParameters.addAll(definition.inputs.map( (input) => Parameter( (p) { p ..name = input.name.namePrintable ..named = true ..toThis = true ..required = input.type.isNonNull; }, ), )), )) ..constructors.add(Constructor( (b) => b ..factory = true ..name = 'fromJson' ..lambda = true ..requiredParameters.add(Parameter( (p) => p ..type = refer('Map') ..name = 'json', )) ..annotations.add(CodeExpression(Code('override'))) ..body = Code('_\$${definition.className}ArgumentsFromJson(json)'), )) ..methods.add(Method( (m) => m ..name = 'toJson' ..lambda = true ..returns = refer('Map') ..annotations.add(CodeExpression(Code('override'))) ..body = Code('_\$${definition.className}ArgumentsToJson(this)'), )) ..fields.addAll(definition.inputs.map( (p) => Field( (f) { f ..name = p.name.namePrintable // TODO: remove this workaround when code_builder includes late field modifier: // https://github.com/dart-lang/code_builder/pull/310 ..type = refer( '${p.type.isNonNull ? 'late ' : ''} ${p.type.namePrintable}') ..annotations .addAll(p.annotations.map((e) => CodeExpression(Code(e)))); if (!p.type.isNonNull) { f.modifier = FieldModifier.final$; } }, ), )), ); } Spec generateQuerySpec(QueryDefinition definition) { return Block((b) => b ..statements.addAll([ Code( "final ${definition.documentOperationName.constantCase} = '${definition.operationName}';"), Code('final ${definition.documentName.constantCase} = '), dart.fromNode(definition.document).code, Code(';'), ])); } /// Generates a [Spec] of a query/mutation class. Spec generateQueryClassSpec(QueryDefinition definition) { final typeDeclaration = definition.inputs.isEmpty ? '${definition.name.namePrintable}, JsonSerializable' : '${definition.name.namePrintable}, ${definition.className}Arguments'; final name = '${definition.className}${definition.suffix}'; final constructor = definition.inputs.isEmpty ? Constructor() : Constructor((b) => b ..optionalParameters.add(Parameter( (p) => p ..name = 'variables' ..toThis = true ..named = true ..required = true, ))); final fields = [ Field( (f) => f ..annotations.add(CodeExpression(Code('override'))) ..modifier = FieldModifier.final$ ..type = refer('DocumentNode', 'package:gql/ast.dart') ..name = 'document' ..assignment = Code(definition.documentName.constantCase), ), Field( (f) => f ..annotations.add(CodeExpression(Code('override'))) ..modifier = FieldModifier.final$ ..type = refer('String') ..name = 'operationName' ..assignment = Code(definition.documentOperationName.constantCase), ), ]; if (definition.inputs.isNotEmpty) { fields.add(Field( (f) => f ..annotations.add(CodeExpression(Code('override'))) ..modifier = FieldModifier.final$ ..type = refer('${definition.className}Arguments') ..name = 'variables', )); } return Class( (b) => b ..name = name ..extend = refer('GraphQLQuery<$typeDeclaration>') ..constructors.add(constructor) ..fields.addAll(fields) ..methods.add(_propsMethod([ 'document', 'operationName${definition.inputs.isNotEmpty ? ', variables' : ''}' ])) ..methods.add(Method( (m) => m ..annotations.add(CodeExpression(Code('override'))) ..returns = refer(definition.name.namePrintable) ..name = 'parse' ..requiredParameters.add(Parameter( (p) => p ..type = refer('Map') ..name = 'json', )) ..lambda = true ..body = Code('${definition.name.namePrintable}.fromJson(json)'), )), ); } /// Gathers and generates a [Spec] of a whole query/mutation and its /// dependencies into a single library file. Spec generateLibrarySpec(LibraryDefinition definition) { final importDirectives = [ Directive.import('package:json_annotation/json_annotation.dart'), Directive.import('package:equatable/equatable.dart'), Directive.import('package:gql/ast.dart'), ]; if (definition.queries.any((q) => q.generateHelpers)) { importDirectives.insertAll( 0, [ Directive.import('package:artemis/artemis.dart'), ], ); } importDirectives.addAll(definition.customImports .map((customImport) => Directive.import(customImport))); final bodyDirectives = [ CodeExpression(Code('part \'${definition.basename}.g.dart\';')), ]; final uniqueDefinitions = definition.queries .map((e) => e.classes.map((e) => e)) .expand((e) => e) .fold>({}, (acc, element) { acc[element.name.name] = element; return acc; }).values; final fragments = uniqueDefinitions.whereType(); final classes = uniqueDefinitions.whereType(); final enums = uniqueDefinitions.whereType(); bodyDirectives.addAll(fragments.map(fragmentClassDefinitionToSpec)); bodyDirectives.addAll( classes.map((cDef) => classDefinitionToSpec(cDef, fragments, classes))); bodyDirectives.addAll(enums.map(enumDefinitionToSpec)); for (final queryDef in definition.queries) { if (queryDef.inputs.isNotEmpty && (queryDef.generateHelpers || queryDef.generateQueries)) { bodyDirectives.add(generateArgumentClassSpec(queryDef)); } if (queryDef.generateHelpers || queryDef.generateQueries) { bodyDirectives.add(generateQuerySpec(queryDef)); } if (queryDef.generateHelpers) { bodyDirectives.add(generateQueryClassSpec(queryDef)); } } return Library( (b) => b ..directives.addAll(importDirectives) ..body.addAll(bodyDirectives), ); } /// Emit a [Spec] into a String, considering Dart formatting. String specToString(Spec spec) { final emitter = DartEmitter(); return DartFormatter().format(spec.accept(emitter).toString()); } /// Generate Dart code typings from a query or mutation and its response from /// a [QueryDefinition] into a buffer. void writeLibraryDefinitionToBuffer( StringBuffer buffer, List ignoreForFile, LibraryDefinition definition, ) { buffer.writeln('// GENERATED CODE - DO NOT MODIFY BY HAND'); if (ignoreForFile.isNotEmpty) { buffer.writeln( '// ignore_for_file: ${Set.from(ignoreForFile).join(', ')}', ); } buffer.write('\n'); buffer.write(specToString(generateLibrarySpec(definition))); } /// Generate an empty file just exporting the library. This is used to avoid /// a breaking change on file generation. String writeLibraryForwarder(LibraryDefinition definition) => '''// GENERATED CODE - DO NOT MODIFY BY HAND export '${definition.basename}.dart'; '''; ================================================ FILE: lib/generator.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:artemis/generator/data/nullable.dart'; import 'package:artemis/visitor/canonical_visitor.dart'; import 'package:artemis/visitor/generator_visitor.dart'; import 'package:artemis/visitor/object_type_definition_visitor.dart'; import 'package:artemis/visitor/schema_definition_visitor.dart'; import 'package:artemis/visitor/type_definition_node_visitor.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:gql/ast.dart'; import 'package:path/path.dart' as p; import './generator/ephemeral_data.dart'; import './generator/errors.dart'; import './generator/graphql_helpers.dart' as gql; import './generator/helpers.dart'; import './schema/options.dart'; typedef OnNewClassFoundCallback = void Function(Context context); /// Enum value for values not mapped in the GraphQL enum final EnumValueDefinition artemisUnknown = EnumValueDefinition( name: EnumValueName(name: 'ARTEMIS_UNKNOWN'), ); /// Generate queries definitions from a GraphQL schema and a list of queries, /// given Artemis options and schema mappings. LibraryDefinition generateLibrary( String path, List gqlDocs, GeneratorOptions options, SchemaMap schemaMap, List fragmentsCommon, DocumentNode schema, ) { final typeDefinitionNodeVisitor = TypeDefinitionNodeVisitor(); schema.accept(typeDefinitionNodeVisitor); final canonicalVisitor = CanonicalVisitor( context: Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: [], currentType: null, currentFieldName: null, currentClassName: null, generatedClasses: [], inputsClasses: [], fragments: [], usedEnums: {}, usedInputObjects: {}, ), ); schema.accept(canonicalVisitor); final documentFragments = gqlDocs .map((doc) => doc.definitions.whereType()) .expand((e) => e) .toList(); final documentsWithoutFragments = gqlDocs.map((doc) { return DocumentNode( definitions: doc.definitions.where((e) => e is! FragmentDefinitionNode).toList(), span: doc.span, ); }).toList(); final queryDefinitions = documentsWithoutFragments .map((doc) => generateDefinitions( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, path: path, document: doc, options: options, schemaMap: schemaMap, fragmentsCommon: [ ...documentFragments, ...fragmentsCommon, ], canonicalVisitor: canonicalVisitor, )) .expand((e) => e) .toList(); final allClassesNames = queryDefinitions .map((def) => def.classes.map((c) => c)) .expand((e) => e) .toList(); allClassesNames.mergeDuplicatesBy((a) => a.name, (a, b) { if (a.name == b.name && a != b) { throw DuplicatedClassesException(a, b); } return a; }); final basename = p.basenameWithoutExtension(path); final customImports = _extractCustomImports(schema, options); return LibraryDefinition( basename: basename, queries: queryDefinitions, customImports: customImports, ); } Set _extractFragments(SelectionSetNode? selectionSet, List fragmentsCommon) { final result = {}; if (selectionSet != null) { selectionSet.selections.whereType().forEach((selection) { result.addAll(_extractFragments(selection.selectionSet, fragmentsCommon)); }); selectionSet.selections .whereType() .forEach((selection) { result.addAll(_extractFragments(selection.selectionSet, fragmentsCommon)); }); selectionSet.selections .whereType() .forEach((selection) { final fragmentDefinitions = fragmentsCommon.where((fragmentDefinition) => fragmentDefinition.name.value == selection.name.value); result.addAll(fragmentDefinitions); for (var fragmentDefinition in fragmentDefinitions) { result.addAll(_extractFragments( fragmentDefinition.selectionSet, fragmentsCommon)); } }); } return result; } /// Generate a query definition from a GraphQL schema and a query, given /// Artemis options and schema mappings. Iterable generateDefinitions({ required DocumentNode schema, required TypeDefinitionNodeVisitor typeDefinitionNodeVisitor, required String path, required DocumentNode document, required GeneratorOptions options, required SchemaMap schemaMap, required List fragmentsCommon, required CanonicalVisitor canonicalVisitor, }) { // final documentFragments = // document.definitions.whereType(); // if (documentFragments.isNotEmpty && fragmentsCommon.isNotEmpty) { // throw FragmentIgnoreException(); // } final operations = document.definitions.whereType().toList(); return operations.map((operation) { // final fragments = [ // ...documentFragments, // ]; final definitions = document.definitions // filtering unused operations .where((e) { return e is! OperationDefinitionNode || e == operation; }).toList(); if (fragmentsCommon.isNotEmpty) { final fragmentsOperation = _extractFragments(operation.selectionSet, fragmentsCommon); definitions.addAll(fragmentsOperation); // fragments.addAll(fragmentsOperation); } final basename = p.basenameWithoutExtension(path).split('.').first; final operationName = operation.name?.value ?? basename; final schemaVisitor = SchemaDefinitionVisitor(); final objectVisitor = ObjectTypeDefinitionVisitor(); schema.accept(schemaVisitor); schema.accept(objectVisitor); String suffix; switch (operation.type) { case OperationType.subscription: suffix = 'Subscription'; break; case OperationType.mutation: suffix = 'Mutation'; break; case OperationType.query: default: suffix = 'Query'; break; } final rootTypeName = (schemaVisitor.schemaDefinitionNode?.operationTypes ?? []) .firstWhereOrNull((e) => e.operation == operation.type) ?.type .name .value ?? suffix; final parentType = objectVisitor.getByName(rootTypeName); if (parentType == null) { throw MissingRootTypeException(rootTypeName); } final name = QueryName.fromPath( path: createPathName([ ClassName(name: operationName), ClassName(name: parentType.name.value) ], schemaMap.namingScheme), ); final context = Context( schema: schema, typeDefinitionNodeVisitor: typeDefinitionNodeVisitor, options: options, schemaMap: schemaMap, path: [ TypeName(name: operationName), TypeName(name: parentType.name.value) ], currentType: parentType, currentFieldName: null, currentClassName: null, generatedClasses: [], inputsClasses: [], fragments: fragmentsCommon, usedEnums: {}, usedInputObjects: {}, ); final visitor = GeneratorVisitor(context: context); final documentDefinitions = DocumentNode(definitions: definitions); documentDefinitions.accept(visitor); return QueryDefinition( name: name, operationName: operationName, document: documentDefinitions, classes: [ ...context.usedEnums .map((e) => canonicalVisitor.enums[e.name]?.call()) .whereType(), ...visitor.context.generatedClasses, ...context.usedInputObjects .map((e) => canonicalVisitor.inputObjects[e.name]?.call()) .whereType(), ], inputs: visitor.context.inputsClasses, generateHelpers: options.generateHelpers, generateQueries: options.generateQueries, suffix: suffix, ); }); } List _extractCustomImports( DocumentNode schema, GeneratorOptions options, ) { final typeVisitor = TypeDefinitionNodeVisitor(); schema.accept(typeVisitor); return typeVisitor.types.values .whereType() .map((type) => gql.importsOfScalar(options, type.name.value)) .expand((i) => i) .toSet() .toList(); } /// Creates class property object ClassProperty createClassProperty({ required ClassPropertyName fieldName, ClassPropertyName? fieldAlias, required Context context, OnNewClassFoundCallback? onNewClassFound, bool markAsUsed = true, }) { if (fieldName.name == context.schemaMap.typeNameField) { return ClassProperty( type: TypeName(name: 'String'), name: fieldName, annotations: ['JsonKey(name: \'${context.schemaMap.typeNameField}\')'], isResolveType: true, ); } var finalFields = []; if (context.currentType is ObjectTypeDefinitionNode) { finalFields = (context.currentType as ObjectTypeDefinitionNode).fields; } else if (context.currentType is InterfaceTypeDefinitionNode) { finalFields = (context.currentType as InterfaceTypeDefinitionNode).fields; } else if (context.currentType is InputObjectTypeDefinitionNode) { finalFields = (context.currentType as InputObjectTypeDefinitionNode).fields; } final regularField = finalFields .whereType() .firstWhereOrNull((f) => f.name.value == fieldName.name); final regularInputField = finalFields .whereType() .firstWhereOrNull((f) => f.name.value == fieldName.name); final fieldType = regularField?.type ?? regularInputField?.type; if (fieldType == null) { throw Exception( '''Field $fieldName was not found in GraphQL type ${context.currentType?.name.value}. Make sure your query is correct and your schema is updated.'''); } final nextType = gql.getTypeByName(context.typeDefinitionNodeVisitor, fieldType); final aliasedContext = context.withAlias( nextFieldName: fieldName, nextClassName: ClassName(name: nextType.name.value), alias: fieldAlias, ); final nextClassName = aliasedContext.fullPathName(); final dartTypeName = gql.buildTypeName( fieldType, context.options, dartType: true, replaceLeafWith: ClassName.fromPath(path: nextClassName), typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, ); logFn(context, aliasedContext.align + 1, '${aliasedContext.path}[${aliasedContext.currentType!.name.value}][${aliasedContext.currentClassName} ${aliasedContext.currentFieldName}] ${fieldAlias == null ? '' : '($fieldAlias) '}-> ${dartTypeName.namePrintable}'); if ((nextType is ObjectTypeDefinitionNode || nextType is UnionTypeDefinitionNode || nextType is InterfaceTypeDefinitionNode) && onNewClassFound != null) { ClassPropertyName? nextFieldName; if (regularField != null) { nextFieldName = ClassPropertyName(name: regularField.name.value); } else if (regularInputField != null) { nextFieldName = ClassPropertyName(name: regularInputField.name.value); } onNewClassFound( aliasedContext.next( nextType: nextType, nextFieldName: nextFieldName, nextClassName: ClassName(name: nextType.name.value), alias: fieldAlias, ofUnion: Nullable(null), ), ); } final name = fieldAlias ?? fieldName; // On custom scalars final jsonKeyAnnotation = {}; if (name.namePrintable != name.name) { jsonKeyAnnotation['name'] = '\'${name.name}\''; } if (nextType is ScalarTypeDefinitionNode) { final scalar = gql.getSingleScalarMap(context.options, nextType.name.value); if (scalar?.customParserImport != null && nextType.name.value == scalar?.graphQLType) { final graphqlTypeName = gql.buildTypeName( fieldType, context.options, dartType: false, typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, ); jsonKeyAnnotation['fromJson'] = 'fromGraphQL${graphqlTypeName.parserSafe}ToDart${dartTypeName.parserSafe}'; jsonKeyAnnotation['toJson'] = 'fromDart${dartTypeName.parserSafe}ToGraphQL${graphqlTypeName.parserSafe}'; } } // On enums else if (nextType is EnumTypeDefinitionNode) { if (markAsUsed) { context.usedEnums.add(EnumName(name: nextType.name.value)); } if (fieldType is ListTypeNode) { final innerDartTypeName = gql.buildTypeName( fieldType.type, context.options, dartType: true, replaceLeafWith: ClassName.fromPath(path: nextClassName), typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, ); jsonKeyAnnotation['unknownEnumValue'] = '${innerDartTypeName.dartTypeSafe}.${artemisUnknown.name.namePrintable}'; } else { jsonKeyAnnotation['unknownEnumValue'] = '${dartTypeName.dartTypeSafe}.${artemisUnknown.name.namePrintable}'; } } final fieldDirectives = regularField?.directives ?? regularInputField?.directives; var annotations = []; if (jsonKeyAnnotation.isNotEmpty) { final jsonKey = jsonKeyAnnotation.entries .map((e) => '${e.key}: ${e.value}') .join(', '); annotations.add('JsonKey($jsonKey)'); } annotations.addAll(proceedDeprecated(fieldDirectives)); return ClassProperty( type: dartTypeName, name: name, annotations: annotations, ); } ================================================ FILE: lib/schema/graphql_query.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; import 'package:json_annotation/json_annotation.dart'; /// A GraphQL query abstraction. This class should be extended automatically /// by Artemis and used with [ArtemisClient]. abstract class GraphQLQuery extends Equatable { /// Instantiates a query or mutation. GraphQLQuery({this.variables}); /// Typed query/mutation variables. final U? variables; /// AST representation of the document to be executed. late final DocumentNode document; /// Operation name used for this query/mutation. final String? operationName = null; /// Parses a JSON map into the response type. T parse(Map json); /// Get variables as a JSON map. Map getVariablesMap() => variables?.toJson() ?? {}; } ================================================ FILE: lib/schema/graphql_response.dart ================================================ import 'package:gql_exec/gql_exec.dart'; /// Encapsulates a GraphQL query/mutation response from server, with typed /// input and responses, and errors. class GraphQLResponse { /// The typed data of this response. final T? data; /// The list of errors in this response. final List? errors; /// If this response has any error. bool get hasErrors => errors != null && errors!.isNotEmpty; /// The [Context] of the [Response] final Context? context; /// Instantiates a GraphQL response. const GraphQLResponse({ this.data, this.errors, this.context, }); } ================================================ FILE: lib/schema/options.dart ================================================ import 'package:json_annotation/json_annotation.dart'; import 'package:yaml/yaml.dart'; // I can't use the default json_serializable flow because the artemis generator // would crash when importing options.dart file. part 'options.g2.dart'; /// This generator options, gathered from `build.yaml` file. @JsonSerializable(fieldRename: FieldRename.snake, anyMap: true) class GeneratorOptions { /// If instances of [GraphQLQuery] should be generated. @JsonKey(defaultValue: true) final bool generateHelpers; /// If query documents and operation names should be generated @JsonKey(defaultValue: true) final bool generateQueries; /// A list of scalar mappings. @JsonKey(defaultValue: []) final List scalarMapping; /// A list of fragments apply for all query files without declare them. final String? fragmentsGlob; /// A list of schema mappings. @JsonKey(defaultValue: []) final List schemaMapping; /// A list of linter rules to ignore /// in generated files. @JsonKey(defaultValue: []) final List ignoreForFile; /// Instantiate generator options. GeneratorOptions({ this.generateHelpers = true, this.generateQueries = true, this.scalarMapping = const [], this.fragmentsGlob, this.schemaMapping = const [], this.ignoreForFile = const [], }); /// Build options from a JSON map. factory GeneratorOptions.fromJson(Map json) => _$GeneratorOptionsFromJson(json); /// Convert this options instance to JSON. Map toJson() => _$GeneratorOptionsToJson(this); } /// Define a Dart type. @JsonSerializable() class DartType { /// Dart type name. final String? name; /// Package imports related to this type. @JsonKey(defaultValue: []) final List imports; /// Instantiate a Dart type. const DartType({ this.name, this.imports = const [], }); /// Build a Dart type from a JSON string or map. factory DartType.fromJson(dynamic json) { if (json is String) { return DartType(name: json); } else if (json is Map) { return _$DartTypeFromJson(json); } else if (json is YamlMap) { return _$DartTypeFromJson({ 'name': json['name'], 'imports': (json['imports'] as YamlList).map((s) => s).toList(), }); } else { throw 'Invalid YAML: $json'; } } /// Convert this Dart type instance to JSON. Map toJson() => _$DartTypeToJson(this); } /// Maps a GraphQL scalar to a Dart type. @JsonSerializable(fieldRename: FieldRename.snake) class ScalarMap { /// The GraphQL type name. @JsonKey(name: 'graphql_type') final String? graphQLType; /// The Dart type linked to this GraphQL type. final DartType? dartType; /// If custom parser would be used. final String? customParserImport; /// Instatiates a scalar mapping. ScalarMap({ this.graphQLType, this.dartType, this.customParserImport, }); /// Build a scalar mapping from a JSON map. factory ScalarMap.fromJson(Map json) => _$ScalarMapFromJson(json); /// Convert this scalar mapping instance to JSON. Map toJson() => _$ScalarMapToJson(this); } /// The naming scheme to be used on generated classes names. enum NamingScheme { /// Default, where the names of previous types are used as prefix of the /// next class. This can generate duplication on certain schemas. pathedWithTypes, /// The names of previous fields are used as prefix of the next class. pathedWithFields, /// Considers only the actual GraphQL class name. This will probably lead to /// duplication and an Artemis error unless user uses aliases. simple, } /// Maps a GraphQL schema to queries files. @JsonSerializable(fieldRename: FieldRename.snake) class SchemaMap { /// The output file of this queries glob. final String? output; /// The GraphQL schema string. final String? schema; /// A [Glob] to find queries files. final String? queriesGlob; /// A [Glob] to find your fragments files. final String? fragmentsGlob; /// The resolve type field used on this schema. @JsonKey(defaultValue: '__typename') final String typeNameField; /// The resolve type field used on this schema. @JsonKey(defaultValue: false) final bool appendTypeName; /// The naming scheme to be used. /// /// - [NamingScheme.pathedWithTypes]: default, where the names of /// previous classes are used to generate the prefix. /// - [NamingScheme.pathedWithFields]: the field names are joined /// together to generate the path. /// - [NamingScheme.simple]: considers only the actual GraphQL class name. /// This will probably lead to duplication and an Artemis error unless you /// use aliases. @JsonKey(unknownEnumValue: NamingScheme.pathedWithTypes) final NamingScheme? namingScheme; /// Instantiates a schema mapping. SchemaMap({ this.output, this.schema, this.queriesGlob, this.fragmentsGlob, this.typeNameField = '__typename', this.appendTypeName = false, this.namingScheme = NamingScheme.pathedWithTypes, }); /// Build a schema mapping from a JSON map. factory SchemaMap.fromJson(Map json) => _$SchemaMapFromJson(json); /// Convert this schema mapping instance to JSON. Map toJson() => _$SchemaMapToJson(this); } ================================================ FILE: lib/schema/options.g2.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'options.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** GeneratorOptions _$GeneratorOptionsFromJson(Map json) { return GeneratorOptions( generateHelpers: json['generate_helpers'] as bool? ?? true, generateQueries: json['generate_queries'] as bool? ?? true, scalarMapping: (json['scalar_mapping'] as List?) ?.map((e) => e == null ? null : ScalarMap.fromJson(Map.from(e as Map))) .toList() ?? [], fragmentsGlob: json['fragments_glob'] as String?, schemaMapping: (json['schema_mapping'] as List?) ?.map( (e) => SchemaMap.fromJson(Map.from(e as Map))) .toList() ?? [], ignoreForFile: (json['ignore_for_file'] as List?) ?.map((e) => e as String) .toList() ?? [], ); } Map _$GeneratorOptionsToJson(GeneratorOptions instance) => { 'generate_helpers': instance.generateHelpers, 'generate_queries': instance.generateQueries, 'scalar_mapping': instance.scalarMapping, 'fragments_glob': instance.fragmentsGlob, 'schema_mapping': instance.schemaMapping, 'ignore_for_file': instance.ignoreForFile, }; DartType _$DartTypeFromJson(Map json) { return DartType( name: json['name'] as String?, imports: (json['imports'] as List?)?.map((e) => e as String).toList() ?? [], ); } Map _$DartTypeToJson(DartType instance) => { 'name': instance.name, 'imports': instance.imports, }; ScalarMap _$ScalarMapFromJson(Map json) { return ScalarMap( graphQLType: json['graphql_type'] as String?, dartType: json['dart_type'] == null ? null : DartType.fromJson(json['dart_type']), customParserImport: json['custom_parser_import'] as String?, ); } Map _$ScalarMapToJson(ScalarMap instance) => { 'graphql_type': instance.graphQLType, 'dart_type': instance.dartType, 'custom_parser_import': instance.customParserImport, }; SchemaMap _$SchemaMapFromJson(Map json) { return SchemaMap( output: json['output'] as String?, schema: json['schema'] as String?, queriesGlob: json['queries_glob'] as String?, fragmentsGlob: json['fragments_glob'] as String?, typeNameField: json['type_name_field'] as String? ?? '__typename', appendTypeName: json['append_type_name'] as bool? ?? false, namingScheme: _$enumDecodeNullable( _$NamingSchemeEnumMap, json['naming_scheme'], unknownValue: NamingScheme.pathedWithTypes), ); } Map _$SchemaMapToJson(SchemaMap instance) => { 'output': instance.output, 'schema': instance.schema, 'queries_glob': instance.queriesGlob, 'fragments_glob': instance.fragmentsGlob, 'type_name_field': instance.typeNameField, 'append_type_name': instance.appendTypeName, 'naming_scheme': _$NamingSchemeEnumMap[instance.namingScheme], }; K _$enumDecode( Map enumValues, Object? source, { K? unknownValue, }) { if (source == null) { throw ArgumentError( 'A value must be provided. Supported values: ' '${enumValues.values.join(', ')}', ); } return enumValues.entries.singleWhere( (e) => e.value == source, orElse: () { if (unknownValue == null) { throw ArgumentError( '`$source` is not one of the supported values: ' '${enumValues.values.join(', ')}', ); } return MapEntry(unknownValue, enumValues.values.first); }, ).key; } K? _$enumDecodeNullable( Map enumValues, dynamic source, { K? unknownValue, }) { if (source == null) { return null; } return _$enumDecode(enumValues, source, unknownValue: unknownValue); } const _$NamingSchemeEnumMap = { NamingScheme.pathedWithTypes: 'pathedWithTypes', NamingScheme.pathedWithFields: 'pathedWithFields', NamingScheme.simple: 'simple', }; ================================================ FILE: lib/transformer/add_typename_transformer.dart ================================================ import 'package:gql/ast.dart'; /// adds type name resolving to all schema types class AppendTypename extends TransformingVisitor { /// type name value final String typeName; /// adds type name resolving to all schema types AppendTypename(this.typeName); @override /// appends type name to OperationDefinitionNode @override OperationDefinitionNode visitOperationDefinitionNode( OperationDefinitionNode node) { // if (node.selectionSet == null) { // return node; // } return OperationDefinitionNode( type: node.type, name: node.name, variableDefinitions: node.variableDefinitions, directives: node.directives, span: node.span, selectionSet: SelectionSetNode( selections: [ ...node.selectionSet.selections.where((element) => (element is! FieldNode) || (element.name.value != typeName)), FieldNode(name: NameNode(value: typeName)), ], ), ); } /// appends type name to FragmentDefinitionNode @override FragmentDefinitionNode visitFragmentDefinitionNode( FragmentDefinitionNode node) { if (node.selectionSet.selections.isEmpty) { return node; } return FragmentDefinitionNode( name: node.name, typeCondition: node.typeCondition, directives: node.directives, span: node.span, selectionSet: SelectionSetNode( selections: [ ...node.selectionSet.selections.where((element) => (element is! FieldNode) || (element.name.value != typeName)), FieldNode(name: NameNode(value: typeName)), ], ), ); } /// appends type name to OperationDefinitionNode @override InlineFragmentNode visitInlineFragmentNode(InlineFragmentNode node) { if (node.selectionSet.selections.isEmpty) { return node; } return InlineFragmentNode( typeCondition: node.typeCondition, directives: node.directives, span: node.span, selectionSet: SelectionSetNode( selections: [ ...node.selectionSet.selections.where((element) => (element is! FieldNode) || (element.name.value != typeName)), FieldNode(name: NameNode(value: typeName)), ], ), ); } /// appends type name to OperationDefinitionNode @override FieldNode visitFieldNode(FieldNode node) { if (node.selectionSet == null) { return node; } return FieldNode( name: node.name, alias: node.alias, arguments: node.arguments, directives: node.directives, span: node.span, selectionSet: SelectionSetNode( selections: [ ...node.selectionSet?.selections.where((element) => (element is! FieldNode) || (element.name.value != typeName)) ?? [], FieldNode(name: NameNode(value: typeName)), ], ), ); } } ================================================ FILE: lib/visitor/canonical_visitor.dart ================================================ import 'package:artemis/generator.dart'; import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:artemis/generator/data/nullable.dart'; import 'package:artemis/generator/ephemeral_data.dart'; import 'package:artemis/generator/helpers.dart'; import 'package:artemis/generator/graphql_helpers.dart' as gql; import 'package:gql/ast.dart'; /// class definition lazy generator typedef ClassDefinitionGenerator = ClassDefinition Function(); /// class definition lazy generator typedef EnumDefinitionGenerator = EnumDefinition Function(); /// Visits canonical types Enums and InputObjects class CanonicalVisitor extends RecursiveVisitor { /// Constructor CanonicalVisitor({ required this.context, }); /// Current context final Context context; /// List of visited input objects final Map inputObjects = {}; /// List of visited enums final Map enums = {}; @override void visitEnumTypeDefinitionNode(EnumTypeDefinitionNode node) { enums[node.name.value] = () { final enumName = EnumName(name: node.name.value); final nextContext = context.sameTypeWithNoPath( alias: enumName, ofUnion: Nullable(null), ); logFn(context, nextContext.align, '-> Enum'); logFn(context, nextContext.align, '<- Generated enum ${enumName.namePrintable}.'); return EnumDefinition( name: enumName, values: node.values .map((ev) => EnumValueDefinition( name: EnumValueName(name: ev.name.value), annotations: proceedDeprecated(ev.directives), )) .toList() ..add(artemisUnknown), ); }; } @override void visitInputObjectTypeDefinitionNode(InputObjectTypeDefinitionNode node) { inputObjects[node.name.value] = () { final name = ClassName(name: node.name.value); final nextContext = context.sameTypeWithNoPath( alias: name, ofUnion: Nullable(null), ); logFn(context, nextContext.align, '-> Input class'); logFn(context, nextContext.align, '┌ ${nextContext.path}[${node.name.value}]'); final properties = []; properties.addAll(node.fields.map((i) { final nextType = gql.getTypeByName(nextContext.typeDefinitionNodeVisitor, i.type); return createClassProperty( fieldName: ClassPropertyName(name: i.name.value), context: nextContext.nextTypeWithNoPath( nextType: node, nextClassName: ClassName(name: nextType.name.value), nextFieldName: ClassName(name: i.name.value), ofUnion: Nullable(null), ), markAsUsed: false, ); })); logFn(context, nextContext.align, '└ ${nextContext.path}[${node.name.value}]'); logFn(context, nextContext.align, '<- Generated input class ${name.namePrintable}.'); return ClassDefinition( isInput: true, name: name, properties: properties, ); }; } } ================================================ FILE: lib/visitor/generator_visitor.dart ================================================ import 'package:artemis/generator.dart'; import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/nullable.dart'; import 'package:artemis/generator/ephemeral_data.dart'; import 'package:artemis/generator/graphql_helpers.dart' as gql; import 'package:artemis/generator/helpers.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:gql/ast.dart'; /// Visitor for types generation class GeneratorVisitor extends RecursiveVisitor { /// Constructor GeneratorVisitor({ required this.context, }); /// Current context final Context context; /// Selection nodes SelectionSetNode? selectionSetNode; /// Class properties final List _classProperties = []; /// Mixnis final List _mixins = []; @override void visitSelectionSetNode(SelectionSetNode node) { final nextContext = context.withAlias(); logFn(context, nextContext.align, '-> Class'); logFn(context, nextContext.align, '┌ ${nextContext.path}[${nextContext.currentType!.name.value}][${nextContext.currentClassName} ${nextContext.currentFieldName}] (${nextContext.alias ?? ''})'); super.visitSelectionSetNode(node); final possibleTypes = {}; if (nextContext.currentType is UnionTypeDefinitionNode || nextContext.currentType is InterfaceTypeDefinitionNode) { // Filter by requested types final keys = node.selections .whereType() .map((n) => n.typeCondition?.on.name.value) .whereType(); final values = keys.map((t) => ClassName.fromPath( path: nextContext.withAlias(alias: ClassName(name: t)).fullPathName())); possibleTypes.addAll(Map.fromIterables(keys, values)); } final partOfUnion = nextContext.ofUnion != null; if (partOfUnion) {} final name = ClassName.fromPath(path: nextContext.fullPathName()); logFn(context, nextContext.align, '└ ${nextContext.path}[${nextContext.currentType!.name.value}][${nextContext.currentClassName} ${nextContext.currentFieldName}] (${nextContext.alias ?? ''})'); logFn(context, nextContext.align, '<- Generated class ${name.namePrintable}.'); nextContext.generatedClasses.add(ClassDefinition( name: name, properties: _classProperties, mixins: _mixins, extension: partOfUnion ? ClassName.fromPath(path: nextContext.rollbackPath().fullPathName()) : null, factoryPossibilities: possibleTypes, )); } @override void visitFieldNode(FieldNode node) { final fieldName = node.name.value; final name = node.alias?.value; final property = createClassProperty( fieldName: ClassPropertyName(name: fieldName), fieldAlias: name != null ? ClassPropertyName(name: name) : null, context: context, onNewClassFound: (nextContext) { node.visitChildren(GeneratorVisitor( context: nextContext, )); }, ); _classProperties.add(property); } @override void visitInlineFragmentNode(InlineFragmentNode node) { logFn(context, context.align + 1, '${context.path}: ... on ${node.typeCondition!.on.name.value}'); final nextType = gql.getTypeByName( context.typeDefinitionNodeVisitor, node.typeCondition!.on); if (nextType.name.value == context.currentType!.name.value) { final visitor = GeneratorVisitor( context: context.nextTypeWithSamePath( nextType: nextType, nextClassName: null, nextFieldName: null, ofUnion: Nullable(context.currentType), inputsClasses: [], fragments: [], ), ); node.selectionSet.visitChildren(visitor); } else { final visitor = GeneratorVisitor( context: context.next( nextType: nextType, nextClassName: ClassName(name: nextType.name.value), nextFieldName: ClassPropertyName(name: nextType.name.value), ofUnion: Nullable(context.currentType), inputsClasses: [], fragments: [], ), ); node.visitChildren(visitor); } } /// void addUsedInputObjectsAndEnums(InputObjectTypeDefinitionNode node) { if (context.usedInputObjects.contains(ClassName(name: node.name.value))) { return; } context.usedInputObjects.add(ClassName(name: node.name.value)); for (final field in node.fields) { final type = gql.getTypeByName(context.typeDefinitionNodeVisitor, field.type); if (type is InputObjectTypeDefinitionNode) { addUsedInputObjectsAndEnums(type); } else if (type is EnumTypeDefinitionNode) { context.usedEnums.add(EnumName(name: type.name.value)); } } } @override void visitVariableDefinitionNode(VariableDefinitionNode node) { final leafType = gql.getTypeByName(context.typeDefinitionNodeVisitor, node.type); final nextClassName = context .nextTypeWithNoPath( nextType: leafType, nextClassName: ClassName(name: leafType.name.value), nextFieldName: ClassName(name: node.variable.name.value), ofUnion: Nullable(null), ) .fullPathName(); final dartTypeName = gql.buildTypeName( node.type, context.options, dartType: true, replaceLeafWith: ClassName.fromPath(path: nextClassName), typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, ); final jsonKeyAnnotation = {}; if (leafType is EnumTypeDefinitionNode) { context.usedEnums.add(EnumName(name: leafType.name.value)); final variableNodeType = node.type; if (variableNodeType is ListTypeNode) { final innerDartTypeName = gql.buildTypeName( variableNodeType.type, context.options, dartType: true, replaceLeafWith: ClassName.fromPath(path: nextClassName), typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, ); jsonKeyAnnotation['unknownEnumValue'] = '${EnumName(name: innerDartTypeName.name).dartTypeSafe}.${artemisUnknown.name.namePrintable}'; } else { jsonKeyAnnotation['unknownEnumValue'] = '${EnumName(name: dartTypeName.name).dartTypeSafe}.${artemisUnknown.name.namePrintable}'; } } else if (leafType is InputObjectTypeDefinitionNode) { addUsedInputObjectsAndEnums(leafType); } else if (leafType is ScalarTypeDefinitionNode) { final scalar = gql.getSingleScalarMap(context.options, leafType.name.value); if (scalar?.customParserImport != null && leafType.name.value == scalar?.graphQLType) { final graphqlTypeName = gql.buildTypeName( node.type, context.options, dartType: false, typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, ); jsonKeyAnnotation['fromJson'] = 'fromGraphQL${graphqlTypeName.parserSafe}ToDart${dartTypeName.parserSafe}'; jsonKeyAnnotation['toJson'] = 'fromDart${dartTypeName.parserSafe}ToGraphQL${graphqlTypeName.parserSafe}'; } } final inputName = QueryInputName(name: node.variable.name.value); if (inputName.namePrintable != inputName.name) { jsonKeyAnnotation['name'] = '\'${inputName.name}\''; } var annotations = []; if (jsonKeyAnnotation.isNotEmpty) { final jsonKey = jsonKeyAnnotation.entries .map((e) => '${e.key}: ${e.value}') .join(', '); annotations.add('JsonKey($jsonKey)'); } context.inputsClasses.add(QueryInput( type: dartTypeName, name: inputName, annotations: annotations, )); } @override void visitFragmentSpreadNode(FragmentSpreadNode node) { logFn(context, context.align + 1, '${context.path}: ... expanding ${node.name.value}'); final fragmentName = FragmentName.fromPath( path: context .sameTypeWithNoPath( alias: FragmentName(name: node.name.value), ofUnion: Nullable(null), ) .fullPathName()); final visitor = GeneratorVisitor( context: context.sameTypeWithNextPath( alias: fragmentName, generatedClasses: [], ofUnion: Nullable(null), log: false, ), ); final fragmentDef = context.fragments .firstWhereOrNull((fragment) => fragment.name.value == node.name.value); fragmentDef?.visitChildren(visitor); _mixins ..add(fragmentName) ..addAll(visitor._mixins); } @override void visitFragmentDefinitionNode(FragmentDefinitionNode node) { final partName = FragmentName(name: node.name.value); final nextContext = context.sameTypeWithNoPath( alias: partName, ofUnion: Nullable(null), ); logFn(context, nextContext.align, '-> Fragment'); logFn(context, nextContext.align, '┌ ${nextContext.path}[${node.name.value}]'); nextContext.fragments.add(node); final nextType = gql.getTypeByName( nextContext.typeDefinitionNodeVisitor, node.typeCondition.on); final visitorContext = Context( schema: context.schema, typeDefinitionNodeVisitor: context.typeDefinitionNodeVisitor, options: context.options, schemaMap: context.schemaMap, path: [nextContext.alias].whereType().toList(), currentType: nextType, currentFieldName: null, currentClassName: null, alias: nextContext.alias, generatedClasses: nextContext.generatedClasses, inputsClasses: [], fragments: [], usedEnums: nextContext.usedEnums, usedInputObjects: nextContext.usedInputObjects, ); final visitor = GeneratorVisitor(context: visitorContext); node.selectionSet.visitChildren(visitor); final otherMixinsProps = nextContext.generatedClasses .whereType() .where((def) => visitor._mixins.contains(def.name)) .map((def) => def.properties) .expand((a) => a) .mergeDuplicatesBy((a) => a.name, (a, b) => a); final fragmentName = FragmentName.fromPath(path: nextContext.fullPathName()); logFn(context, nextContext.align, '└ ${nextContext.path}[${node.name.value}]'); logFn(context, nextContext.align, '<- Generated fragment ${fragmentName.namePrintable}.'); nextContext.generatedClasses.add( FragmentClassDefinition( name: fragmentName, properties: visitor._classProperties.followedBy(otherMixinsProps).toList(), ), ); } } ================================================ FILE: lib/visitor/object_type_definition_visitor.dart ================================================ import 'package:gql/ast.dart'; /// Visits all object definition nodes recursively class ObjectTypeDefinitionVisitor extends RecursiveVisitor { /// Stores all object definition nodes Iterable types = []; @override void visitObjectTypeDefinitionNode( ObjectTypeDefinitionNode node, ) { types = types.followedBy([node]); super.visitObjectTypeDefinitionNode(node); } /// Gets object type definition node by operation name ObjectTypeDefinitionNode? getByName(String name) { final type = types.where((type) => type.name.value == name); if (type.isNotEmpty) { return type.first; } return null; } } ================================================ FILE: lib/visitor/operation_type_definition_visitor.dart ================================================ import 'package:gql/ast.dart'; /// Visits all operation definition nodes recursively class OperationTypeDefinitionNodeVisitor extends RecursiveVisitor { /// Stores all operation definition nodes Iterable types = []; @override void visitOperationTypeDefinitionNode( OperationTypeDefinitionNode node, ) { types = types.followedBy([node]); super.visitOperationTypeDefinitionNode(node); } /// Gets operation type definition node by operation name OperationTypeDefinitionNode? getByType(OperationType operationType) { final type = types.where((type) => type.operation == operationType); if (type.isNotEmpty) { return type.first; } return null; } } ================================================ FILE: lib/visitor/schema_definition_visitor.dart ================================================ import 'package:gql/ast.dart'; /// Visits the schema definition node. class SchemaDefinitionVisitor extends RecursiveVisitor { /// Store the schema definition. SchemaDefinitionNode? schemaDefinitionNode; @override void visitSchemaDefinitionNode( SchemaDefinitionNode node, ) { schemaDefinitionNode = node; } } ================================================ FILE: lib/visitor/type_definition_node_visitor.dart ================================================ import 'package:gql/ast.dart'; List> _defaultScalars = ['Boolean', 'Float', 'ID', 'Int', 'String'] .map((e) => MapEntry( e, ScalarTypeDefinitionNode( name: NameNode(value: e), ))) .toList(); /// Visits all type definition nodes recursively class TypeDefinitionNodeVisitor extends RecursiveVisitor { /// Stores all type definition nodes Map types = Map.fromEntries(_defaultScalars); @override void visitObjectTypeDefinitionNode( ObjectTypeDefinitionNode node, ) { types[node.name.value] = node; super.visitObjectTypeDefinitionNode(node); } @override void visitScalarTypeDefinitionNode( ScalarTypeDefinitionNode node, ) { types[node.name.value] = node; super.visitScalarTypeDefinitionNode(node); } @override void visitInterfaceTypeDefinitionNode( InterfaceTypeDefinitionNode node, ) { types[node.name.value] = node; super.visitInterfaceTypeDefinitionNode(node); } @override void visitUnionTypeDefinitionNode( UnionTypeDefinitionNode node, ) { types[node.name.value] = node; super.visitUnionTypeDefinitionNode(node); } @override void visitInputObjectTypeDefinitionNode( InputObjectTypeDefinitionNode node, ) { types[node.name.value] = node; super.visitInputObjectTypeDefinitionNode(node); } @override void visitEnumTypeDefinitionNode( EnumTypeDefinitionNode node, ) { types[node.name.value] = node; super.visitEnumTypeDefinitionNode(node); } /// Gets type definition node by type name TypeDefinitionNode? getByName(String name) { final type = types[name]; if (type != null) { return type; } return null; } } ================================================ FILE: pubspec.yaml ================================================ name: artemis version: 7.13.1 description: Build dart types from GraphQL schemas and queries (using Introspection Query). homepage: https://github.com/comigor/artemis environment: sdk: ">=2.18.0 <3.0.0" dependencies: build_config: ^1.1.1 code_builder: ^4.4.0 build: ^2.3.1 collection: ^1.17.1 dart_style: ^2.3.0 equatable: ^2.0.5 glob: ^2.1.1 gql_code_builder: ^0.7.1 gql_dedupe_link: ^2.0.3+1 gql_exec: ^0.4.3 gql_http_link: ^0.4.5 gql_link: ^0.5.1 gql: ^0.14.0 http: ^0.13.5 json_annotation: ^4.8.0 path: ^1.8.3 recase: ^4.1.0 source_gen: ^1.2.7 yaml: ^3.1.1 dev_dependencies: args: ^2.4.0 build_runner: ^2.3.3 build_test: ^2.1.7 json_serializable: ^6.6.1 build_resolvers: ^2.2.0 test: ^1.22.2 logging: ^1.1.1 lints: ^2.0.1 ================================================ FILE: test/generator/helpers_test.dart ================================================ import 'package:test/test.dart'; import 'package:artemis/generator/helpers.dart'; void main() { group('On removeDuplicatedBy helper', () { test('It will return a new iterable.', () { final it = [ {'a': 1}, {'a': 2}, ]; final anotherIt = it.removeDuplicatedBy((i) => i['a']); expect(anotherIt == it, false); }); test('It will not mutate the old iterable.', () { final it = [ {'a': 1}, {'a': 2}, {'a': 2}, {'a': 1}, ]; final copyOfIt = List.from(it); it.removeDuplicatedBy((i) => i['a']); expect(it, equals(copyOfIt)); }); test('It will keep only the first entry that matches the function.', () { final it = [ {'a': 1, 'first': true}, {'a': 2, 'first': true}, {'a': 2, 'first': false}, {'a': 1, 'first': false}, ]; final anotherIt = it.removeDuplicatedBy((i) => i['a']); expect( anotherIt, equals([ {'a': 1, 'first': true}, {'a': 2, 'first': true}, ])); }); test('Iterable function can return anything.', () { final it = [ {'a': 1, 'first': true}, {'a': 2, 'first': true}, {'a': 2, 'first': false}, {'a': 1, 'first': false}, ]; final anotherIt = it.removeDuplicatedBy((i) => i); expect(anotherIt, equals(it)); }); }); group('On mergeDuplicatesBy helper', () { test('It will return a new iterable.', () { final it = [ {'a': 1}, {'a': 2}, ]; final anotherIt = it.mergeDuplicatesBy((i) => i, (i, _) => i); expect(anotherIt == it, false); }); test('It can behave like removeDuplicatedBy.', () { final it = [ {'a': 1, 'first': true}, {'a': 2, 'first': true}, {'a': 2, 'first': false}, {'a': 1, 'first': false}, ]; final anotherIt = it.mergeDuplicatesBy((i) => i['a'], (i, _) => i); expect( anotherIt, equals([ {'a': 1, 'first': true}, {'a': 2, 'first': true}, ])); }); test('It can return a list of the last elements based on fn.', () { final it = [ {'a': 1, 'last': false}, {'a': 2, 'last': false}, {'a': 2, 'last': true}, {'a': 1, 'last': true}, ]; final anotherIt = it.mergeDuplicatesBy((i) => i['a'], (_, i) => i); expect( anotherIt, equals([ {'a': 1, 'last': true}, {'a': 2, 'last': true}, ])); }); }); } ================================================ FILE: test/generator/print_helpers_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:artemis/generator/print_helpers.dart'; import 'package:gql/language.dart'; import 'package:test/test.dart'; void main() { group('On printCustomEnum', () { test('It will throw if name is empty.', () { expect( () => enumDefinitionToSpec( EnumDefinition(name: EnumName(name: ''), values: [])), throwsA(TypeMatcher())); }); test('It will throw if values is empty.', () { // expect( // () => enumDefinitionToSpec( // EnumDefinition(name: EnumName(name: 'Name'), values: null)), // throwsA(TypeMatcher())); expect( () => enumDefinitionToSpec( EnumDefinition(name: EnumName(name: 'Name'), values: [])), throwsA(TypeMatcher())); }); test('It will generate an Enum declaration.', () { final definition = EnumDefinition(name: EnumName(name: 'Name'), values: [ EnumValueDefinition( name: EnumValueName(name: 'Option'), ), EnumValueDefinition( name: EnumValueName(name: 'anotherOption'), ), EnumValueDefinition( name: EnumValueName(name: 'third_option'), ), EnumValueDefinition( name: EnumValueName(name: 'FORTH_OPTION'), ), ]); final str = specToString(enumDefinitionToSpec(definition)); expect(str, '''enum Name { @JsonValue('Option') option, @JsonValue('anotherOption') anotherOption, @JsonValue('third_option') thirdOption, @JsonValue('FORTH_OPTION') forthOption, } '''); }); test('It will ignore duplicate options.', () { final definition = EnumDefinition(name: EnumName(name: 'Name'), values: [ EnumValueDefinition( name: EnumValueName(name: 'Option'), ), EnumValueDefinition( name: EnumValueName(name: 'AnotherOption'), ), EnumValueDefinition( name: EnumValueName(name: 'Option'), ), EnumValueDefinition( name: EnumValueName(name: 'AnotherOption'), ), ]); final str = specToString(enumDefinitionToSpec(definition)); expect(str, '''enum Name { @JsonValue('Option') option, @JsonValue('AnotherOption') anotherOption, } '''); }); }); group('On printCustomFragmentClass', () { test('It will throw if name is null or empty.', () { expect( () => fragmentClassDefinitionToSpec(FragmentClassDefinition( name: FragmentName(name: ''), properties: [])), throwsA(TypeMatcher())); }); test('It will generate an Mixins declarations.', () { final definition = FragmentClassDefinition( name: FragmentName(name: 'FragmentMixin'), properties: [ ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'name')), ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'name'), annotations: ['override']), ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'name'), annotations: ['Test']), ]); final str = specToString(fragmentClassDefinitionToSpec(definition)); expect(str, '''mixin FragmentMixin { Type? name; @override Type? name; @Test Type? name; } '''); }); }); group('On printCustomClass', () { test('It will throw if name is empty.', () { // expect( // () => classDefinitionToSpec( // ClassDefinition(name: null, properties: []), [], []), // throwsA(TypeMatcher())); expect( () => classDefinitionToSpec( ClassDefinition(name: ClassName(name: ''), properties: []), [], []), throwsA(TypeMatcher())); }); test('It can generate a class without properties.', () { final definition = ClassDefinition(name: ClassName(name: 'AClass'), properties: []); final str = specToString(classDefinitionToSpec(definition, [], [])); expect(str, '''@JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin { AClass(); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); @override List get props => []; @override Map toJson() => _\$AClassToJson(this); } '''); }); test('"Mixins" will be included to class.', () { final definition = ClassDefinition( name: ClassName(name: 'AClass'), properties: [], extension: ClassName(name: 'AnotherClass')); final str = specToString(classDefinitionToSpec(definition, [], [])); expect(str, '''@JsonSerializable(explicitToJson: true) class AClass extends AnotherClass with EquatableMixin { AClass(); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); @override List get props => []; @override Map toJson() => _\$AClassToJson(this); } '''); }); test( 'factoryPossibilities and typeNameField are used to generated a branch factory.', () { final definition = ClassDefinition( name: ClassName(name: 'AClass'), properties: [], factoryPossibilities: { 'ASubClass': ClassName(name: 'ASubClass'), 'BSubClass': ClassName(name: 'BSubClass'), }, typeNameField: ClassPropertyName(name: '__typename'), ); final str = specToString(classDefinitionToSpec(definition, [], [])); expect(str, r'''@JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin { AClass(); factory AClass.fromJson(Map json) { switch (json['__typename'].toString()) { case r'ASubClass': return ASubClass.fromJson(json); case r'BSubClass': return BSubClass.fromJson(json); default: } return _$AClassFromJson(json); } @override List get props => []; @override Map toJson() { switch ($$typename) { case r'ASubClass': return (this as ASubClass).toJson(); case r'BSubClass': return (this as BSubClass).toJson(); default: } return _$AClassToJson(this); } } '''); }); test('It can have properties.', () { final definition = ClassDefinition(name: ClassName(name: 'AClass'), properties: [ ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'name')), ClassProperty( type: TypeName(name: 'AnotherType'), name: ClassPropertyName(name: 'anotherName')), ]); final str = specToString(classDefinitionToSpec(definition, [], [])); expect(str, '''@JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin { AClass(); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); Type? name; AnotherType? anotherName; @override List get props => [name, anotherName]; @override Map toJson() => _\$AClassToJson(this); } '''); }); test( 'Its properties can be an override or have a custom annotation, or both.', () { final definition = ClassDefinition(name: ClassName(name: 'AClass'), properties: [ ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'nameA')), ClassProperty( type: TypeName(name: 'AnnotatedProperty'), name: ClassPropertyName(name: 'nameB'), annotations: ['Hey()']), ClassProperty( type: TypeName(name: 'OverridenProperty'), name: ClassPropertyName(name: 'nameC'), annotations: ['override']), ClassProperty( type: TypeName(name: 'AllAtOnce'), name: ClassPropertyName(name: 'nameD'), annotations: ['override', 'Ho()']), ]); final str = specToString(classDefinitionToSpec(definition, [], [])); expect(str, '''@JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin { AClass(); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); Type? nameA; @Hey() AnnotatedProperty? nameB; @override OverridenProperty? nameC; @override @Ho() AllAtOnce? nameD; @override List get props => [nameA, nameB, nameC, nameD]; @override Map toJson() => _\$AClassToJson(this); } '''); }); test( 'Mixins can be included and its properties will be considered on props getter', () { final definition = ClassDefinition( name: ClassName(name: 'AClass'), properties: [], mixins: [FragmentName(name: 'FragmentMixin')]); final str = specToString(classDefinitionToSpec(definition, [ FragmentClassDefinition( name: FragmentName(name: 'FragmentMixin'), properties: [ ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'name')), ]) ], [])); expect(str, '''@JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin, FragmentMixin { AClass(); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); @override List get props => [name]; @override Map toJson() => _\$AClassToJson(this); } '''); }); test('It can be an input object (and have a named parameter constructor).', () { final definition = ClassDefinition( name: ClassName(name: 'AClass'), properties: [ ClassProperty( type: TypeName(name: 'Type'), name: ClassPropertyName(name: 'name')), ClassProperty( type: TypeName(name: 'AnotherType', isNonNull: true), name: ClassPropertyName(name: 'anotherName')), ], isInput: true, ); final str = specToString(classDefinitionToSpec(definition, [], [])); expect(str, '''@JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin { AClass({ this.name, required this.anotherName, }); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); Type? name; late AnotherType anotherName; @override List get props => [name, anotherName]; @override Map toJson() => _\$AClassToJson(this); } '''); }); }); group('On generateQueryClassSpec', () { test('It will throw if basename is null or empty.', () { expect(() => generateLibrarySpec(LibraryDefinition(basename: '')), throwsA(TypeMatcher())); }); test('It will throw if query name/type is null or empty.', () { expect( () => generateQueryClassSpec( QueryDefinition( name: QueryName(name: ''), operationName: 'Type', document: parseString('query test_query {}')), ), throwsA(TypeMatcher()), ); expect( () => generateQueryClassSpec( QueryDefinition( name: QueryName(name: 'Type'), operationName: '', document: parseString('query test_query {}')), ), throwsA( TypeMatcher(), ), ); expect( () => generateQueryClassSpec( QueryDefinition( name: QueryName(name: ''), operationName: 'test_query', document: parseString('query test_query {}')), ), throwsA( TypeMatcher(), ), ); }); test('It should generated an empty file by default.', () { final buffer = StringBuffer(); final definition = LibraryDefinition(basename: r'test_query.graphql'); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; '''); }); test('When there are custom imports, they are included.', () { final buffer = StringBuffer(); final definition = LibraryDefinition( basename: r'test_query.graphql', customImports: ['some_file.dart']); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; import 'some_file.dart'; part 'test_query.graphql.g.dart'; '''); }); test('When generateHelpers is true, an execute fn is generated.', () { final buffer = StringBuffer(); final definition = LibraryDefinition( basename: r'test_query.graphql', queries: [ QueryDefinition( name: QueryName(name: 'test_query'), operationName: 'test_query', document: parseString('query test_query {}'), generateHelpers: true, ) ], ); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; final TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'test_query'; final TEST_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'test_query'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: []), ) ]); class TestQueryQuery extends GraphQLQuery { TestQueryQuery(); @override final DocumentNode document = TEST_QUERY_QUERY_DOCUMENT; @override final String operationName = TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override List get props => [document, operationName]; @override TestQuery parse(Map json) => TestQuery.fromJson(json); } '''); }); test( 'When generateHelpers is false and generateQueries is true, an execute fn is generated.', () { final buffer = StringBuffer(); final definition = LibraryDefinition( basename: r'test_query.graphql', queries: [ QueryDefinition( name: QueryName(name: 'test_query'), operationName: 'test_query', document: parseString('query test_query {}'), generateHelpers: false, generateQueries: true, ) ], ); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; final TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'test_query'; final TEST_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'test_query'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: []), ) ]); '''); }); test('The generated execute fn could have input.', () { final buffer = StringBuffer(); final definition = LibraryDefinition(basename: r'test_query.graphql', queries: [ QueryDefinition( name: QueryName(name: 'test_query'), operationName: 'test_query', document: parseString('query test_query {}'), generateHelpers: true, inputs: [ QueryInput( type: TypeName(name: 'Type'), name: QueryInputName(name: 'name')) ], ), ]); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class TestQueryArguments extends JsonSerializable with EquatableMixin { TestQueryArguments({this.name}); @override factory TestQueryArguments.fromJson(Map json) => _$TestQueryArgumentsFromJson(json); final Type? name; @override List get props => [name]; @override Map toJson() => _$TestQueryArgumentsToJson(this); } final TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'test_query'; final TEST_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'test_query'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: []), ) ]); class TestQueryQuery extends GraphQLQuery { TestQueryQuery({required this.variables}); @override final DocumentNode document = TEST_QUERY_QUERY_DOCUMENT; @override final String operationName = TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override final TestQueryArguments variables; @override List get props => [document, operationName, variables]; @override TestQuery parse(Map json) => TestQuery.fromJson(json); } '''); }); test('Will generate an Arguments class', () { final definition = QueryDefinition( name: QueryName(name: 'test_query'), operationName: 'test_query', document: parseString('query test_query {}'), generateHelpers: true, inputs: [ QueryInput( type: TypeName(name: 'Type'), name: QueryInputName(name: 'name')) ], ); final str = specToString(generateArgumentClassSpec(definition)); expect(str, '''@JsonSerializable(explicitToJson: true) class TestQueryArguments extends JsonSerializable with EquatableMixin { TestQueryArguments({this.name}); @override factory TestQueryArguments.fromJson(Map json) => _\$TestQueryArgumentsFromJson(json); final Type? name; @override List get props => [name]; @override Map toJson() => _\$TestQueryArgumentsToJson(this); } '''); }); test('Will generate a Query Class', () { final definition = QueryDefinition( name: QueryName(name: 'test_query'), operationName: 'test_query', document: parseString('query test_query {}'), generateHelpers: true, inputs: [ QueryInput( type: TypeName(name: 'Type'), name: QueryInputName(name: 'name')) ], suffix: 'Query', ); final str = specToString(generateQuerySpec(definition)) + specToString(generateQueryClassSpec(definition)); expect(str, r'''final TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'test_query'; final TEST_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'test_query'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: []), ) ]); class TestQueryQuery extends GraphQLQuery { TestQueryQuery({required this.variables}); @override final DocumentNode document = TEST_QUERY_QUERY_DOCUMENT; @override final String operationName = TEST_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override final TestQueryArguments variables; @override List get props => [document, operationName, variables]; @override TestQuery parse(Map json) => TestQuery.fromJson(json); } '''); }); test('It will accept and write class/enum definitions.', () { final buffer = StringBuffer(); final definition = LibraryDefinition(basename: r'test_query.graphql', queries: [ QueryDefinition( name: QueryName(name: 'test_query'), operationName: 'test_query', document: parseString('query test_query {}'), classes: [ EnumDefinition(name: EnumName(name: 'SomeEnum'), values: [ EnumValueDefinition( name: EnumValueName(name: 'Value'), ) ]), ClassDefinition(name: ClassName(name: 'AClass'), properties: []) ], ), ]); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class AClass extends JsonSerializable with EquatableMixin { AClass(); factory AClass.fromJson(Map json) => _\$AClassFromJson(json); @override List get props => []; @override Map toJson() => _\$AClassToJson(this); } enum SomeEnum { @JsonValue('Value') value, } '''); }); }); test('Should not add ignore_for_file when ignoreForFile is null', () { final buffer = StringBuffer(); final definition = LibraryDefinition(basename: r'test_query.graphql'); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; '''); }); test('Should not add ignore_for_file when ignoreForFile is empty', () { final buffer = StringBuffer(); final definition = LibraryDefinition(basename: r'test_query.graphql'); final ignoreForFile = []; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; '''); }); test('Should add // ignore_for_file: ... when ignoreForFile is not empty', () { final buffer = StringBuffer(); final definition = LibraryDefinition(basename: r'test_query.graphql'); final ignoreForFile = ['my_rule_1', 'my_rule_2']; writeLibraryDefinitionToBuffer(buffer, ignoreForFile, definition); expect(buffer.toString(), '''// GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: my_rule_1, my_rule_2 import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'test_query.graphql.g.dart'; '''); }); } ================================================ FILE: test/helpers.dart ================================================ import 'package:artemis/builder.dart'; import 'package:artemis/generator/data/data.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; import 'package:collection/collection.dart'; final bool Function(Iterable, Iterable) listEquals = const DeepCollectionEquality.unordered().equals; Future testGenerator({ required String query, required LibraryDefinition libraryDefinition, required String generatedFile, required String schema, String namingScheme = 'pathedWithTypes', bool appendTypeName = false, bool generateHelpers = false, bool generateQueries = false, Map builderOptionsMap = const {}, Map sourceAssetsMap = const {}, Map outputsMap = const {}, }) async { Logger.root.level = Level.INFO; final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ if (!generateHelpers) 'generate_helpers': false, if (!generateQueries) 'generate_queries': false, 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'queries_glob': 'queries/**.graphql', 'output': 'lib/query.graphql.dart', 'naming_scheme': namingScheme, 'append_type_name': appendTypeName, } ], ...builderOptionsMap, })); anotherBuilder.onBuild = expectAsync1((definition) { log.fine(definition); expect(definition, libraryDefinition); }, count: 1); return await testBuilder( anotherBuilder, { 'a|api.schema.graphql': schema, 'a|queries/query.graphql': query, ...sourceAssetsMap, }, outputs: { 'a|lib/query.graphql.dart': generatedFile, ...outputsMap, }, onLog: print, ); } Future testNaming({ required String query, required String schema, required List expectedNames, required String namingScheme, bool shouldFail = false, }) { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'generate_queries': false, 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'queries_glob': 'queries/**.graphql', 'output': 'lib/query.dart', 'naming_scheme': namingScheme, } ], })); if (!shouldFail) { anotherBuilder.onBuild = expectAsync1((definition) { final names = definition.queries.first.classes .map((e) => e.name.namePrintable) .toSet(); log.fine(names); expect(names.toSet(), equals(expectedNames.toSet())); }, count: 1); } return testBuilder( anotherBuilder, { 'a|api.schema.graphql': schema, 'a|queries/query.graphql': query, }, onLog: print, ); } ================================================ FILE: test/query_generator/aliases/alias_on_leaves_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On aliases', () { test( 'Leaves can be aliased', () async => testGenerator( query: query, schema: r''' schema { query: Response } type Response { s: String o: SomeObject ob: [SomeObject] } type SomeObject { e: MyEnum } enum MyEnum { A B } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query some_query { thisIsAString: s o { thisIsAnEnum: e } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_Response'), operationName: r'some_query', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'SomeQuery$_Response$_SomeObject'), properties: [ ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'thisIsAnEnum'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_Response'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'thisIsAString'), isResolveType: false), ClassProperty( type: TypeName(name: r'SomeQuery$_Response$_SomeObject'), name: ClassPropertyName(name: r'o'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$Response$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$Response$SomeObject(); factory SomeQuery$Response$SomeObject.fromJson(Map json) => _$SomeQuery$Response$SomeObjectFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? thisIsAnEnum; @override List get props => [thisIsAnEnum]; @override Map toJson() => _$SomeQuery$Response$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$Response extends JsonSerializable with EquatableMixin { SomeQuery$Response(); factory SomeQuery$Response.fromJson(Map json) => _$SomeQuery$ResponseFromJson(json); String? thisIsAString; SomeQuery$Response$SomeObject? o; @override List get props => [thisIsAString, o]; @override Map toJson() => _$SomeQuery$ResponseToJson(this); } enum MyEnum { @JsonValue('A') a, @JsonValue('B') b, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/aliases/alias_on_object_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On aliases', () { test( 'Objects can be aliased', () async => testGenerator( query: query, schema: r''' schema { query: QueryResponse } type QueryResponse { s: String o: SomeObject ob: [SomeObject] } type SomeObject { st: String str: String } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query some_query { s o { st } anotherObject: ob { str } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_QueryResponse'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'st'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse$_anotherObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'str'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: TypeName(name: r'SomeQuery$_QueryResponse$_SomeObject'), name: ClassPropertyName(name: r'o'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'SomeQuery$_QueryResponse$_anotherObject'), isNonNull: false), name: ClassPropertyName(name: r'anotherObject'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse$SomeObject(); factory SomeQuery$QueryResponse$SomeObject.fromJson( Map json) => _$SomeQuery$QueryResponse$SomeObjectFromJson(json); String? st; @override List get props => [st]; @override Map toJson() => _$SomeQuery$QueryResponse$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse$AnotherObject extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse$AnotherObject(); factory SomeQuery$QueryResponse$AnotherObject.fromJson( Map json) => _$SomeQuery$QueryResponse$AnotherObjectFromJson(json); String? str; @override List get props => [str]; @override Map toJson() => _$SomeQuery$QueryResponse$AnotherObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse(); factory SomeQuery$QueryResponse.fromJson(Map json) => _$SomeQuery$QueryResponseFromJson(json); String? s; SomeQuery$QueryResponse$SomeObject? o; List? anotherObject; @override List get props => [s, o, anotherObject]; @override Map toJson() => _$SomeQuery$QueryResponseToJson(this); } '''; ================================================ FILE: test/query_generator/append_type_name_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On query generation', () { test( 'Appends typename', () async => testGenerator( appendTypeName: true, namingScheme: 'pathedWithFields', query: r''' query custom { q { e } } ''', schema: r''' schema { query: QueryRoot } type QueryRoot { q: QueryResponse } type QueryResponse { e: String } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_q'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'e'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_q'), name: ClassPropertyName(name: r'q'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$Q extends JsonSerializable with EquatableMixin { Custom$QueryRoot$Q(); factory Custom$QueryRoot$Q.fromJson(Map json) => _$Custom$QueryRoot$QFromJson(json); String? e; @JsonKey(name: '__typename') String? $$typename; @override List get props => [e, $$typename]; @override Map toJson() => _$Custom$QueryRoot$QToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$Q? q; @JsonKey(name: '__typename') String? $$typename; @override List get props => [q, $$typename]; @override Map toJson() => _$Custom$QueryRootToJson(this); } ''', generateHelpers: false)); test( 'Do not appends typename if it exist', () async => testGenerator( appendTypeName: true, namingScheme: 'pathedWithFields', query: r''' query custom { q { e __typename } __typename } ''', schema: r''' schema { query: QueryRoot } type QueryRoot { q: QueryResponse } type QueryResponse { e: String } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_q'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'e'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_q'), name: ClassPropertyName(name: r'q'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$Q extends JsonSerializable with EquatableMixin { Custom$QueryRoot$Q(); factory Custom$QueryRoot$Q.fromJson(Map json) => _$Custom$QueryRoot$QFromJson(json); String? e; @JsonKey(name: '__typename') String? $$typename; @override List get props => [e, $$typename]; @override Map toJson() => _$Custom$QueryRoot$QToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$Q? q; @JsonKey(name: '__typename') String? $$typename; @override List get props => [q, $$typename]; @override Map toJson() => _$Custom$QueryRootToJson(this); } ''', generateHelpers: false)); test( 'Appends typename on fragment', () async => testGenerator( appendTypeName: true, namingScheme: 'pathedWithFields', query: r''' query custom { q { ...QueryResponse } } fragment QueryResponse on QueryResponse { e } ''', schema: r''' schema { query: QueryRoot } type QueryRoot { q: QueryResponse } type QueryResponse { e: String } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_q'), properties: [ ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], mixins: [FragmentName(name: r'QueryResponseMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_q'), name: ClassPropertyName(name: r'q'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'QueryResponseMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'e'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ]) ], generateHelpers: false, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin QueryResponseMixin { String? e; @JsonKey(name: '__typename') String? $$typename; } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$Q extends JsonSerializable with EquatableMixin, QueryResponseMixin { Custom$QueryRoot$Q(); factory Custom$QueryRoot$Q.fromJson(Map json) => _$Custom$QueryRoot$QFromJson(json); @JsonKey(name: '__typename') String? $$typename; @override List get props => [e, $$typename]; @override Map toJson() => _$Custom$QueryRoot$QToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$Q? q; @JsonKey(name: '__typename') String? $$typename; @override List get props => [q, $$typename]; @override Map toJson() => _$Custom$QueryRootToJson(this); } ''', generateHelpers: false)); test( 'Appends typename on union', () async => testGenerator( appendTypeName: true, namingScheme: 'pathedWithFields', query: r''' query custom { q { ... on TypeA { a }, ... on TypeB { b } } } ''', schema: r''' schema { query: QueryRoot } type QueryRoot { q: SomeUnion } union SomeUnion = TypeA | TypeB type TypeA { a: Int } type TypeB { b: Int } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_q$_typeA'), properties: [ ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'a'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], extension: ClassName(name: r'Custom$_QueryRoot$_q'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_q$_typeB'), properties: [ ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'b'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], extension: ClassName(name: r'Custom$_QueryRoot$_q'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_q'), properties: [ ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: { r'TypeA': ClassName(name: r'Custom$_QueryRoot$_q$_TypeA'), r'TypeB': ClassName(name: r'Custom$_QueryRoot$_q$_TypeB') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_q'), name: ClassPropertyName(name: r'q'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$Q$TypeA extends Custom$QueryRoot$Q with EquatableMixin { Custom$QueryRoot$Q$TypeA(); factory Custom$QueryRoot$Q$TypeA.fromJson(Map json) => _$Custom$QueryRoot$Q$TypeAFromJson(json); int? a; @JsonKey(name: '__typename') @override String? $$typename; @override List get props => [a, $$typename]; @override Map toJson() => _$Custom$QueryRoot$Q$TypeAToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$Q$TypeB extends Custom$QueryRoot$Q with EquatableMixin { Custom$QueryRoot$Q$TypeB(); factory Custom$QueryRoot$Q$TypeB.fromJson(Map json) => _$Custom$QueryRoot$Q$TypeBFromJson(json); int? b; @JsonKey(name: '__typename') @override String? $$typename; @override List get props => [b, $$typename]; @override Map toJson() => _$Custom$QueryRoot$Q$TypeBToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$Q extends JsonSerializable with EquatableMixin { Custom$QueryRoot$Q(); factory Custom$QueryRoot$Q.fromJson(Map json) { switch (json['__typename'].toString()) { case r'TypeA': return Custom$QueryRoot$Q$TypeA.fromJson(json); case r'TypeB': return Custom$QueryRoot$Q$TypeB.fromJson(json); default: } return _$Custom$QueryRoot$QFromJson(json); } @JsonKey(name: '__typename') String? $$typename; @override List get props => [$$typename]; @override Map toJson() { switch ($$typename) { case r'TypeA': return (this as Custom$QueryRoot$Q$TypeA).toJson(); case r'TypeB': return (this as Custom$QueryRoot$Q$TypeB).toJson(); default: } return _$Custom$QueryRoot$QToJson(this); } } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$Q? q; @JsonKey(name: '__typename') String? $$typename; @override List get props => [q, $$typename]; @override Map toJson() => _$Custom$QueryRootToJson(this); } ''', generateHelpers: false)); test( 'Appends typename to common fragments', () async => testGenerator( appendTypeName: true, query: r''' query custom { q { ...QueryResponse } } ''', schema: r''' schema { query: QueryRoot } type QueryRoot { q: QueryResponse } type QueryResponse { e: String } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_QueryResponse'), properties: [ ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], mixins: [FragmentName(name: r'QueryResponseMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName( name: r'Custom$_QueryRoot$_QueryResponse'), name: ClassPropertyName(name: r'q'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'QueryResponseMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'e'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ]) ], generateHelpers: true, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin QueryResponseMixin { String? e; @JsonKey(name: '__typename') String? $$typename; } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$QueryResponse extends JsonSerializable with EquatableMixin, QueryResponseMixin { Custom$QueryRoot$QueryResponse(); factory Custom$QueryRoot$QueryResponse.fromJson(Map json) => _$Custom$QueryRoot$QueryResponseFromJson(json); @JsonKey(name: '__typename') String? $$typename; @override List get props => [e, $$typename]; @override Map toJson() => _$Custom$QueryRoot$QueryResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$QueryResponse? q; @JsonKey(name: '__typename') String? $$typename; @override List get props => [q, $$typename]; @override Map toJson() => _$Custom$QueryRootToJson(this); } final CUSTOM_QUERY_DOCUMENT_OPERATION_NAME = 'custom'; final CUSTOM_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'custom'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'q'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'QueryResponse'), directives: [], ), FieldNode( name: NameNode(value: '__typename'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ), FieldNode( name: NameNode(value: '__typename'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ), FragmentDefinitionNode( name: NameNode(value: 'QueryResponse'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'QueryResponse'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'e'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: '__typename'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ), ]); class CustomQuery extends GraphQLQuery { CustomQuery(); @override final DocumentNode document = CUSTOM_QUERY_DOCUMENT; @override final String operationName = CUSTOM_QUERY_DOCUMENT_OPERATION_NAME; @override List get props => [document, operationName]; @override Custom$QueryRoot parse(Map json) => Custom$QueryRoot.fromJson(json); } ''', builderOptionsMap: {'fragments_glob': '**.frag'}, sourceAssetsMap: { 'a|fragment.frag': r''' fragment QueryResponse on QueryResponse { e } ''' }, generateHelpers: true, ), ); }); } ================================================ FILE: test/query_generator/ast_schema/field_not_found_mutation_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On AST schema', () { test( 'Field was not found on mutation', () async => testGenerator( query: query, schema: r''' schema { mutation: MutationRoot } input CreateThingInput { clientId: ID! message: String } type Thing { id: ID! message: String } type CreateThingResponse { thing: Thing } type MutationRoot { createThing(input: CreateThingInput): CreateThingResponse } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' mutation createThing($createThingInput: CreateThingInput) { createThing(input: $createThingInput) { thing { id message } } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'CreateThing$_MutationRoot'), operationName: r'createThing', classes: [ ClassDefinition( name: ClassName( name: r'CreateThing$_MutationRoot$_CreateThingResponse$_Thing'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'message'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName( name: r'CreateThing$_MutationRoot$_CreateThingResponse'), properties: [ ClassProperty( type: TypeName( name: r'CreateThing$_MutationRoot$_CreateThingResponse$_Thing'), name: ClassPropertyName(name: r'thing'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CreateThing$_MutationRoot'), properties: [ ClassProperty( type: TypeName( name: r'CreateThing$_MutationRoot$_CreateThingResponse'), name: ClassPropertyName(name: r'createThing'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CreateThingInput'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'clientId'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'message'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'CreateThingInput'), name: QueryInputName(name: r'createThingInput')) ], generateHelpers: false, suffix: r'Mutation') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class CreateThing$MutationRoot$CreateThingResponse$Thing extends JsonSerializable with EquatableMixin { CreateThing$MutationRoot$CreateThingResponse$Thing(); factory CreateThing$MutationRoot$CreateThingResponse$Thing.fromJson( Map json) => _$CreateThing$MutationRoot$CreateThingResponse$ThingFromJson(json); late String id; String? message; @override List get props => [id, message]; @override Map toJson() => _$CreateThing$MutationRoot$CreateThingResponse$ThingToJson(this); } @JsonSerializable(explicitToJson: true) class CreateThing$MutationRoot$CreateThingResponse extends JsonSerializable with EquatableMixin { CreateThing$MutationRoot$CreateThingResponse(); factory CreateThing$MutationRoot$CreateThingResponse.fromJson( Map json) => _$CreateThing$MutationRoot$CreateThingResponseFromJson(json); CreateThing$MutationRoot$CreateThingResponse$Thing? thing; @override List get props => [thing]; @override Map toJson() => _$CreateThing$MutationRoot$CreateThingResponseToJson(this); } @JsonSerializable(explicitToJson: true) class CreateThing$MutationRoot extends JsonSerializable with EquatableMixin { CreateThing$MutationRoot(); factory CreateThing$MutationRoot.fromJson(Map json) => _$CreateThing$MutationRootFromJson(json); CreateThing$MutationRoot$CreateThingResponse? createThing; @override List get props => [createThing]; @override Map toJson() => _$CreateThing$MutationRootToJson(this); } @JsonSerializable(explicitToJson: true) class CreateThingInput extends JsonSerializable with EquatableMixin { CreateThingInput({ required this.clientId, this.message, }); factory CreateThingInput.fromJson(Map json) => _$CreateThingInputFromJson(json); late String clientId; String? message; @override List get props => [clientId, message]; @override Map toJson() => _$CreateThingInputToJson(this); } '''; ================================================ FILE: test/query_generator/ast_schema/input_types_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On AST schema', () { test( 'Input object was not generated', () async => testGenerator( query: query, schema: r''' schema { mutation: MutationRoot } input OtherObjectInput { id: ID! } input CreateThingInput { clientId: ID! message: String shares: [OtherObjectInput!] } type Thing { id: ID! message: String } type CreateThingResponse { thing: Thing } type MutationRoot { createThing(input: CreateThingInput): CreateThingResponse } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' mutation createThing($createThingInput: CreateThingInput) { createThing(input: $createThingInput) { thing { id message } } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'CreateThing$_MutationRoot'), operationName: r'createThing', classes: [ ClassDefinition( name: ClassName( name: r'CreateThing$_MutationRoot$_CreateThingResponse$_Thing'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'message'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName( name: r'CreateThing$_MutationRoot$_CreateThingResponse'), properties: [ ClassProperty( type: TypeName( name: r'CreateThing$_MutationRoot$_CreateThingResponse$_Thing'), name: ClassPropertyName(name: r'thing'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CreateThing$_MutationRoot'), properties: [ ClassProperty( type: TypeName( name: r'CreateThing$_MutationRoot$_CreateThingResponse'), name: ClassPropertyName(name: r'createThing'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CreateThingInput'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'clientId'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'message'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'OtherObjectInput', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'shares'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true), ClassDefinition( name: ClassName(name: r'OtherObjectInput'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'CreateThingInput'), name: QueryInputName(name: r'createThingInput')) ], generateHelpers: false, suffix: r'Mutation') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class CreateThing$MutationRoot$CreateThingResponse$Thing extends JsonSerializable with EquatableMixin { CreateThing$MutationRoot$CreateThingResponse$Thing(); factory CreateThing$MutationRoot$CreateThingResponse$Thing.fromJson( Map json) => _$CreateThing$MutationRoot$CreateThingResponse$ThingFromJson(json); late String id; String? message; @override List get props => [id, message]; @override Map toJson() => _$CreateThing$MutationRoot$CreateThingResponse$ThingToJson(this); } @JsonSerializable(explicitToJson: true) class CreateThing$MutationRoot$CreateThingResponse extends JsonSerializable with EquatableMixin { CreateThing$MutationRoot$CreateThingResponse(); factory CreateThing$MutationRoot$CreateThingResponse.fromJson( Map json) => _$CreateThing$MutationRoot$CreateThingResponseFromJson(json); CreateThing$MutationRoot$CreateThingResponse$Thing? thing; @override List get props => [thing]; @override Map toJson() => _$CreateThing$MutationRoot$CreateThingResponseToJson(this); } @JsonSerializable(explicitToJson: true) class CreateThing$MutationRoot extends JsonSerializable with EquatableMixin { CreateThing$MutationRoot(); factory CreateThing$MutationRoot.fromJson(Map json) => _$CreateThing$MutationRootFromJson(json); CreateThing$MutationRoot$CreateThingResponse? createThing; @override List get props => [createThing]; @override Map toJson() => _$CreateThing$MutationRootToJson(this); } @JsonSerializable(explicitToJson: true) class CreateThingInput extends JsonSerializable with EquatableMixin { CreateThingInput({ required this.clientId, this.message, this.shares, }); factory CreateThingInput.fromJson(Map json) => _$CreateThingInputFromJson(json); late String clientId; String? message; List? shares; @override List get props => [clientId, message, shares]; @override Map toJson() => _$CreateThingInputToJson(this); } @JsonSerializable(explicitToJson: true) class OtherObjectInput extends JsonSerializable with EquatableMixin { OtherObjectInput({required this.id}); factory OtherObjectInput.fromJson(Map json) => _$OtherObjectInputFromJson(json); late String id; @override List get props => [id]; @override Map toJson() => _$OtherObjectInputToJson(this); } '''; ================================================ FILE: test/query_generator/ast_schema/missing_schema_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On AST schema', () { test( 'When schema declaration is missing', () async => testGenerator( query: query, schema: r''' type Query { a: String } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query { a } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_Query'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_Query'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'a'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$Query extends JsonSerializable with EquatableMixin { Query$Query(); factory Query$Query.fromJson(Map json) => _$Query$QueryFromJson(json); String? a; @override List get props => [a]; @override Map toJson() => _$Query$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/ast_schema/multiple_schema_mappint_test.dart ================================================ import 'package:artemis/builder.dart'; import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:test/test.dart'; void main() { group('Multiple schema mapping', () { test( 'Should search for definitions in correct schema', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': true, 'schema_mapping': [ { 'schema': 'schemaA.graphql', 'queries_glob': 'queries/queryA.graphql', 'output': 'lib/outputA.graphql.dart', 'naming_scheme': 'pathedWithFields', }, { 'schema': 'schemaB.graphql', 'queries_glob': 'queries/queryB.graphql', 'output': 'lib/outputB.graphql.dart', 'naming_scheme': 'pathedWithFields', } ], })); var count = 0; anotherBuilder.onBuild = expectAsync1((definition) { log.fine(definition); if (count == 0) { expect(definition, libraryDefinitionA); } if (count == 1) { expect(definition, libraryDefinitionB); } count++; }, count: 2); return await testBuilder( anotherBuilder, { 'a|schemaA.graphql': schemaA, 'a|schemaB.graphql': schemaB, 'a|queries/queryA.graphql': queryA, 'a|queries/queryB.graphql': queryB, }, outputs: { 'a|lib/outputA.graphql.dart': generatedFileA, 'a|lib/outputB.graphql.dart': generatedFileB, }, onLog: print, ); }, ); }); } const schemaA = r''' schema { query: Query } type Query { articles: [Article!] } type Article { id: ID! title: String! articleType: ArticleType! } enum ArticleType { NEWS TUTORIAL } '''; const schemaB = r''' schema { query: Query } type Query { repositories(notificationTypes: [NotificationOptionInput]): [Repository!] } type Repository { id: ID! title: String! privacy: Privacy! status: Status! } enum Privacy { PRIVATE PUBLIC } enum Status { ARCHIVED NORMAL } input NotificationOptionInput { type: NotificationType enabled: Boolean } enum NotificationType { ACTIVITY_MESSAGE ACTIVITY_REPLY FOLLOWING ACTIVITY_MENTION } '''; const queryA = r''' query BrowseArticles { articles { id title articleType } } '''; const queryB = r''' query BrowseRepositories($notificationTypes: [NotificationOptionInput]) { repositories(notificationTypes: $notificationTypes) { id title privacy status } } '''; final LibraryDefinition libraryDefinitionA = LibraryDefinition(basename: r'outputA.graphql', queries: [ QueryDefinition( name: QueryName(name: r'BrowseArticles$_Query'), operationName: r'BrowseArticles', classes: [ EnumDefinition(name: EnumName(name: r'ArticleType'), values: [ EnumValueDefinition(name: EnumValueName(name: r'NEWS')), EnumValueDefinition(name: EnumValueName(name: r'TUTORIAL')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'BrowseArticles$_Query$_articles'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'title'), isResolveType: false), ClassProperty( type: TypeName(name: r'ArticleType', isNonNull: true), name: ClassPropertyName(name: r'articleType'), annotations: [ r'JsonKey(unknownEnumValue: ArticleType.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'BrowseArticles$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'BrowseArticles$_Query$_articles', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'articles'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: true, suffix: r'Query') ]); final libraryDefinitionB = LibraryDefinition(basename: r'outputB.graphql', queries: [ QueryDefinition( name: QueryName(name: r'BrowseRepositories$_Query'), operationName: r'BrowseRepositories', classes: [ EnumDefinition(name: EnumName(name: r'Privacy'), values: [ EnumValueDefinition(name: EnumValueName(name: r'PRIVATE')), EnumValueDefinition(name: EnumValueName(name: r'PUBLIC')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), EnumDefinition(name: EnumName(name: r'Status'), values: [ EnumValueDefinition(name: EnumValueName(name: r'ARCHIVED')), EnumValueDefinition(name: EnumValueName(name: r'NORMAL')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), EnumDefinition(name: EnumName(name: r'NotificationType'), values: [ EnumValueDefinition(name: EnumValueName(name: r'ACTIVITY_MESSAGE')), EnumValueDefinition(name: EnumValueName(name: r'ACTIVITY_REPLY')), EnumValueDefinition(name: EnumValueName(name: r'FOLLOWING')), EnumValueDefinition(name: EnumValueName(name: r'ACTIVITY_MENTION')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'BrowseRepositories$_Query$_repositories'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'title'), isResolveType: false), ClassProperty( type: TypeName(name: r'Privacy', isNonNull: true), name: ClassPropertyName(name: r'privacy'), annotations: [ r'JsonKey(unknownEnumValue: Privacy.artemisUnknown)' ], isResolveType: false), ClassProperty( type: TypeName(name: r'Status', isNonNull: true), name: ClassPropertyName(name: r'status'), annotations: [ r'JsonKey(unknownEnumValue: Status.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'BrowseRepositories$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'BrowseRepositories$_Query$_repositories', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'repositories'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'NotificationOptionInput'), properties: [ ClassProperty( type: TypeName(name: r'NotificationType'), name: ClassPropertyName(name: r'type'), annotations: [ r'JsonKey(unknownEnumValue: NotificationType.artemisUnknown)' ], isResolveType: false), ClassProperty( type: DartTypeName(name: r'bool'), name: ClassPropertyName(name: r'enabled'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: ListOfTypeName( typeName: TypeName(name: r'NotificationOptionInput'), isNonNull: false), name: QueryInputName(name: r'notificationTypes')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFileA = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'outputA.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class BrowseArticles$Query$Articles extends JsonSerializable with EquatableMixin { BrowseArticles$Query$Articles(); factory BrowseArticles$Query$Articles.fromJson(Map json) => _$BrowseArticles$Query$ArticlesFromJson(json); late String id; late String title; @JsonKey(unknownEnumValue: ArticleType.artemisUnknown) late ArticleType articleType; @override List get props => [id, title, articleType]; @override Map toJson() => _$BrowseArticles$Query$ArticlesToJson(this); } @JsonSerializable(explicitToJson: true) class BrowseArticles$Query extends JsonSerializable with EquatableMixin { BrowseArticles$Query(); factory BrowseArticles$Query.fromJson(Map json) => _$BrowseArticles$QueryFromJson(json); List? articles; @override List get props => [articles]; @override Map toJson() => _$BrowseArticles$QueryToJson(this); } enum ArticleType { @JsonValue('NEWS') news, @JsonValue('TUTORIAL') tutorial, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } final BROWSE_ARTICLES_QUERY_DOCUMENT_OPERATION_NAME = 'BrowseArticles'; final BROWSE_ARTICLES_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'BrowseArticles'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'articles'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'title'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'articleType'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ) ]), ) ]); class BrowseArticlesQuery extends GraphQLQuery { BrowseArticlesQuery(); @override final DocumentNode document = BROWSE_ARTICLES_QUERY_DOCUMENT; @override final String operationName = BROWSE_ARTICLES_QUERY_DOCUMENT_OPERATION_NAME; @override List get props => [document, operationName]; @override BrowseArticles$Query parse(Map json) => BrowseArticles$Query.fromJson(json); } '''; const generatedFileB = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'outputB.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class BrowseRepositories$Query$Repositories extends JsonSerializable with EquatableMixin { BrowseRepositories$Query$Repositories(); factory BrowseRepositories$Query$Repositories.fromJson( Map json) => _$BrowseRepositories$Query$RepositoriesFromJson(json); late String id; late String title; @JsonKey(unknownEnumValue: Privacy.artemisUnknown) late Privacy privacy; @JsonKey(unknownEnumValue: Status.artemisUnknown) late Status status; @override List get props => [id, title, privacy, status]; @override Map toJson() => _$BrowseRepositories$Query$RepositoriesToJson(this); } @JsonSerializable(explicitToJson: true) class BrowseRepositories$Query extends JsonSerializable with EquatableMixin { BrowseRepositories$Query(); factory BrowseRepositories$Query.fromJson(Map json) => _$BrowseRepositories$QueryFromJson(json); List? repositories; @override List get props => [repositories]; @override Map toJson() => _$BrowseRepositories$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class NotificationOptionInput extends JsonSerializable with EquatableMixin { NotificationOptionInput({ this.type, this.enabled, }); factory NotificationOptionInput.fromJson(Map json) => _$NotificationOptionInputFromJson(json); @JsonKey(unknownEnumValue: NotificationType.artemisUnknown) NotificationType? type; bool? enabled; @override List get props => [type, enabled]; @override Map toJson() => _$NotificationOptionInputToJson(this); } enum Privacy { @JsonValue('PRIVATE') private, @JsonValue('PUBLIC') public, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } enum Status { @JsonValue('ARCHIVED') archived, @JsonValue('NORMAL') normal, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } enum NotificationType { @JsonValue('ACTIVITY_MESSAGE') activityMessage, @JsonValue('ACTIVITY_REPLY') activityReply, @JsonValue('FOLLOWING') following, @JsonValue('ACTIVITY_MENTION') activityMention, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } @JsonSerializable(explicitToJson: true) class BrowseRepositoriesArguments extends JsonSerializable with EquatableMixin { BrowseRepositoriesArguments({this.notificationTypes}); @override factory BrowseRepositoriesArguments.fromJson(Map json) => _$BrowseRepositoriesArgumentsFromJson(json); final List? notificationTypes; @override List get props => [notificationTypes]; @override Map toJson() => _$BrowseRepositoriesArgumentsToJson(this); } final BROWSE_REPOSITORIES_QUERY_DOCUMENT_OPERATION_NAME = 'BrowseRepositories'; final BROWSE_REPOSITORIES_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'BrowseRepositories'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'notificationTypes')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'NotificationOptionInput'), isNonNull: false, ), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'repositories'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'notificationTypes'), value: VariableNode(name: NameNode(value: 'notificationTypes')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'title'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'privacy'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'status'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ) ]), ) ]); class BrowseRepositoriesQuery extends GraphQLQuery { BrowseRepositoriesQuery({required this.variables}); @override final DocumentNode document = BROWSE_REPOSITORIES_QUERY_DOCUMENT; @override final String operationName = BROWSE_REPOSITORIES_QUERY_DOCUMENT_OPERATION_NAME; @override final BrowseRepositoriesArguments variables; @override List get props => [document, operationName, variables]; @override BrowseRepositories$Query parse(Map json) => BrowseRepositories$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/deprecated/deprecated_enum_value_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:gql/language.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On deprecated value', () { test( 'Enum values can be deprecated', () async => testGenerator( query: query, schema: r''' schema { query: QueryResponse } type QueryResponse { someValue: StarWarsMovies } enum StarWarsMovies { NEW_HOPE @deprecated(reason: "deprecated movie") EMPIRE JEDI } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query some_query { someValue } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( document: parseString(query), name: QueryName(name: r'SomeQuery$_QueryResponse'), operationName: 'some_query', classes: [ EnumDefinition( name: EnumName(name: r'StarWarsMovies'), values: [ EnumValueDefinition( name: EnumValueName(name: 'NEW_HOPE'), annotations: [ r"Deprecated('deprecated movie')", ], ), EnumValueDefinition( name: EnumValueName(name: 'EMPIRE'), ), EnumValueDefinition( name: EnumValueName(name: 'JEDI'), ), EnumValueDefinition( name: EnumValueName(name: 'ARTEMIS_UNKNOWN'), ), ], ), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse'), properties: [ ClassProperty( type: TypeName(name: r'StarWarsMovies'), name: ClassPropertyName(name: r'someValue'), // isOverride: false, annotations: [ r'JsonKey(unknownEnumValue: StarWarsMovies.artemisUnknown)', ]) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse(); factory SomeQuery$QueryResponse.fromJson(Map json) => _$SomeQuery$QueryResponseFromJson(json); @JsonKey(unknownEnumValue: StarWarsMovies.artemisUnknown) StarWarsMovies? someValue; @override List get props => [someValue]; @override Map toJson() => _$SomeQuery$QueryResponseToJson(this); } enum StarWarsMovies { @Deprecated('deprecated movie') @JsonValue('NEW_HOPE') newHope, @JsonValue('EMPIRE') empire, @JsonValue('JEDI') jedi, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/deprecated/deprecated_field_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On deprecated', () { test( 'Fields can be deprecated', () async => testGenerator( query: query, schema: r''' schema { query: QueryResponse } type QueryResponse { someObject: SomeObject @deprecated(reason: "message") someObjects: [SomeObject] } type SomeObject { someField: String deprecatedField: String @deprecated(reason: "message 2") } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query some_query { deprecatedObject: someObject { someField deprecatedField } someObjects { someField deprecatedField } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_QueryResponse'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse$_deprecatedObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'someField'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'deprecatedField'), annotations: [r'''Deprecated('message 2')'''], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'someField'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'deprecatedField'), annotations: [r'''Deprecated('message 2')'''], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse'), properties: [ ClassProperty( type: TypeName( name: r'SomeQuery$_QueryResponse$_deprecatedObject'), name: ClassPropertyName(name: r'deprecatedObject'), annotations: [r'''Deprecated('message')'''], isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'SomeQuery$_QueryResponse$_SomeObject'), isNonNull: false), name: ClassPropertyName(name: r'someObjects'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse$DeprecatedObject extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse$DeprecatedObject(); factory SomeQuery$QueryResponse$DeprecatedObject.fromJson( Map json) => _$SomeQuery$QueryResponse$DeprecatedObjectFromJson(json); String? someField; @Deprecated('message 2') String? deprecatedField; @override List get props => [someField, deprecatedField]; @override Map toJson() => _$SomeQuery$QueryResponse$DeprecatedObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse$SomeObject(); factory SomeQuery$QueryResponse$SomeObject.fromJson( Map json) => _$SomeQuery$QueryResponse$SomeObjectFromJson(json); String? someField; @Deprecated('message 2') String? deprecatedField; @override List get props => [someField, deprecatedField]; @override Map toJson() => _$SomeQuery$QueryResponse$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse(); factory SomeQuery$QueryResponse.fromJson(Map json) => _$SomeQuery$QueryResponseFromJson(json); @Deprecated('message') SomeQuery$QueryResponse$DeprecatedObject? deprecatedObject; List? someObjects; @override List get props => [deprecatedObject, someObjects]; @override Map toJson() => _$SomeQuery$QueryResponseToJson(this); } '''; ================================================ FILE: test/query_generator/deprecated/deprecated_input_object_field_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On mutations', () { test( 'The mutation class will be suffixed as Mutation', () async => testGenerator( query: query, schema: r''' schema { mutation: MutationRoot } type MutationRoot { mut(input: Input!): MutationResponse } type MutationResponse { s: String } input Input { s: String! d: String @deprecated(reason: "deprecated input field") } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } const query = r''' mutation custom($input: Input!) { mut(input: $input) { s } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_MutationRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_MutationRoot$_MutationResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_MutationRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_MutationRoot$_MutationResponse'), name: ClassPropertyName(name: r'mut'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'd'), annotations: [r'''Deprecated('deprecated input field')'''], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Mutation') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$MutationRoot$MutationResponse extends JsonSerializable with EquatableMixin { Custom$MutationRoot$MutationResponse(); factory Custom$MutationRoot$MutationResponse.fromJson( Map json) => _$Custom$MutationRoot$MutationResponseFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$Custom$MutationRoot$MutationResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$MutationRoot extends JsonSerializable with EquatableMixin { Custom$MutationRoot(); factory Custom$MutationRoot.fromJson(Map json) => _$Custom$MutationRootFromJson(json); Custom$MutationRoot$MutationResponse? mut; @override List get props => [mut]; @override Map toJson() => _$Custom$MutationRootToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({ required this.s, this.d, }); factory Input.fromJson(Map json) => _$InputFromJson(json); late String s; @Deprecated('deprecated input field') String? d; @override List get props => [s, d]; @override Map toJson() => _$InputToJson(this); } @JsonSerializable(explicitToJson: true) class CustomArguments extends JsonSerializable with EquatableMixin { CustomArguments({required this.input}); @override factory CustomArguments.fromJson(Map json) => _$CustomArgumentsFromJson(json); late Input input; @override List get props => [input]; @override Map toJson() => _$CustomArgumentsToJson(this); } final CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME = 'custom'; final CUSTOM_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: 'custom'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mut'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class CustomMutation extends GraphQLQuery { CustomMutation({required this.variables}); @override final DocumentNode document = CUSTOM_MUTATION_DOCUMENT; @override final String operationName = CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME; @override final CustomArguments variables; @override List get props => [document, operationName, variables]; @override Custom$MutationRoot parse(Map json) => Custom$MutationRoot.fromJson(json); } '''; ================================================ FILE: test/query_generator/deprecated/deprecated_interface_field_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On interfaces', () { test( 'On interfaces', () async => testGenerator( query: query, schema: graphQLSchema, libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query custom($id: ID!) { nodeById(id: $id) { id deprecatedField ... on User { ...UserFrag } ... on ChatMessage { message user { ...UserFrag } } } } fragment UserFrag on User { id username } '''; // https://graphql-code-generator.com/#live-demo final String graphQLSchema = r''' scalar String scalar ID schema { query: Query } type Query { nodeById(id: ID!): Node } interface Node { id: ID! deprecatedField: String @deprecated(reason: "deprecated interface field") } type User implements Node { id: ID! username: String! deprecatedField: String } type ChatMessage implements Node { id: ID! message: String! user: User! } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Query'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_User'), extension: ClassName(name: r'Custom$_Query$_Node'), mixins: [FragmentName(name: r'UserFragMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_ChatMessage$_User'), mixins: [FragmentName(name: r'UserFragMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_ChatMessage'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'message'), isResolveType: false), ClassProperty( type: TypeName( name: r'Custom$_Query$_Node$_ChatMessage$_User', isNonNull: true), name: ClassPropertyName(name: r'user'), isResolveType: false) ], extension: ClassName(name: r'Custom$_Query$_Node'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'deprecatedField'), annotations: [ r'''Deprecated('deprecated interface field')''' ], isResolveType: false) ], factoryPossibilities: { r'User': ClassName(name: r'Custom$_Query$_Node$_User'), r'ChatMessage': ClassName(name: r'Custom$_Query$_Node$_ChatMessage') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_Query$_Node'), name: ClassPropertyName(name: r'nodeById'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'UserFragMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'username'), isResolveType: false) ]) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'id')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin UserFragMixin { late String id; late String username; } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$User extends Custom$Query$Node with EquatableMixin, UserFragMixin { Custom$Query$Node$User(); factory Custom$Query$Node$User.fromJson(Map json) => _$Custom$Query$Node$UserFromJson(json); @override List get props => [id, username]; @override Map toJson() => _$Custom$Query$Node$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$ChatMessage$User extends JsonSerializable with EquatableMixin, UserFragMixin { Custom$Query$Node$ChatMessage$User(); factory Custom$Query$Node$ChatMessage$User.fromJson( Map json) => _$Custom$Query$Node$ChatMessage$UserFromJson(json); @override List get props => [id, username]; @override Map toJson() => _$Custom$Query$Node$ChatMessage$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$ChatMessage extends Custom$Query$Node with EquatableMixin { Custom$Query$Node$ChatMessage(); factory Custom$Query$Node$ChatMessage.fromJson(Map json) => _$Custom$Query$Node$ChatMessageFromJson(json); late String message; late Custom$Query$Node$ChatMessage$User user; @override List get props => [message, user]; @override Map toJson() => _$Custom$Query$Node$ChatMessageToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node extends JsonSerializable with EquatableMixin { Custom$Query$Node(); factory Custom$Query$Node.fromJson(Map json) { switch (json['__typename'].toString()) { case r'User': return Custom$Query$Node$User.fromJson(json); case r'ChatMessage': return Custom$Query$Node$ChatMessage.fromJson(json); default: } return _$Custom$Query$NodeFromJson(json); } late String id; @Deprecated('deprecated interface field') String? deprecatedField; @override List get props => [id, deprecatedField]; @override Map toJson() { switch ($$typename) { case r'User': return (this as Custom$Query$Node$User).toJson(); case r'ChatMessage': return (this as Custom$Query$Node$ChatMessage).toJson(); default: } return _$Custom$Query$NodeToJson(this); } } @JsonSerializable(explicitToJson: true) class Custom$Query extends JsonSerializable with EquatableMixin { Custom$Query(); factory Custom$Query.fromJson(Map json) => _$Custom$QueryFromJson(json); Custom$Query$Node? nodeById; @override List get props => [nodeById]; @override Map toJson() => _$Custom$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/enums/enum_duplication_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('Enum duplication', () { test( 'Enum duplication should be properly handeled', () async => testGenerator( query: query, namingScheme: 'pathedWithFields', schema: r''' schema { query: Query } type Query { q: QueryResponse qList: [QueryResponse] } type QueryResponse { e: MyEnum } enum MyEnum { A B } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, sourceAssetsMap: { 'a|queries/another_query.graphql': anotherQuery, }, ), ); }); } const query = r''' query custom { q { e } } '''; const anotherQuery = r''' query customList { qList { e } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Query'), operationName: r'custom', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'Custom$_Query$_q'), properties: [ ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_Query$_q'), name: ClassPropertyName(name: r'q'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query'), QueryDefinition( name: QueryName(name: r'CustomList$_Query'), operationName: r'customList', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'CustomList$_Query$_qList'), properties: [ ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CustomList$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'CustomList$_Query$_qList'), isNonNull: false), name: ClassPropertyName(name: r'qList'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$Query$Q extends JsonSerializable with EquatableMixin { Custom$Query$Q(); factory Custom$Query$Q.fromJson(Map json) => _$Custom$Query$QFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; @override List get props => [e]; @override Map toJson() => _$Custom$Query$QToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query extends JsonSerializable with EquatableMixin { Custom$Query(); factory Custom$Query.fromJson(Map json) => _$Custom$QueryFromJson(json); Custom$Query$Q? q; @override List get props => [q]; @override Map toJson() => _$Custom$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class CustomList$Query$QList extends JsonSerializable with EquatableMixin { CustomList$Query$QList(); factory CustomList$Query$QList.fromJson(Map json) => _$CustomList$Query$QListFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; @override List get props => [e]; @override Map toJson() => _$CustomList$Query$QListToJson(this); } @JsonSerializable(explicitToJson: true) class CustomList$Query extends JsonSerializable with EquatableMixin { CustomList$Query(); factory CustomList$Query.fromJson(Map json) => _$CustomList$QueryFromJson(json); List? qList; @override List get props => [qList]; @override Map toJson() => _$CustomList$QueryToJson(this); } enum MyEnum { @JsonValue('A') a, @JsonValue('B') b, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/enums/enum_list_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On enum list', () { test( 'Enums as lists are generated correctly', () async => testGenerator( query: query, libraryDefinition: libraryDefinition, generatedFile: generatedFile, schema: r''' schema { query: QueryRoot } type QueryRoot { q: QueryResponse } type QueryResponse { le: [MyEnum] } enum MyEnum { A B }''', ), ); }); } const query = r''' query custom { q { le } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_QueryResponse'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'MyEnum'), isNonNull: false), name: ClassPropertyName(name: r'le'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_QueryResponse'), name: ClassPropertyName(name: r'q'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$QueryResponse extends JsonSerializable with EquatableMixin { Custom$QueryRoot$QueryResponse(); factory Custom$QueryRoot$QueryResponse.fromJson(Map json) => _$Custom$QueryRoot$QueryResponseFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) List? le; @override List get props => [le]; @override Map toJson() => _$Custom$QueryRoot$QueryResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$QueryResponse? q; @override List get props => [q]; @override Map toJson() => _$Custom$QueryRootToJson(this); } enum MyEnum { @JsonValue('A') a, @JsonValue('B') b, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/enums/filter_enum_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On enums', () { test( 'Enums that are not used on result or input will be filtered out', () async => testGenerator( query: query, schema: r''' schema { query: QueryRoot } type QueryRoot { q(e: input_enum!, i: Input!): QueryResponse } input Input { e: _InputInputEnum } type QueryResponse { e: MyEnum } enum MyEnum { A B } enum input_enum { C D } enum _InputInputEnum { _E _F _new new } type UnusedObject { e: UnusedEnum } input UnusedInput { u: UnusedEnum } enum UnusedEnum { G H } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query custom($e: input_enum!, $i: Input!) { q(e: $e, i: $i) { e } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), EnumDefinition(name: EnumName(name: r'input_enum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'C')), EnumValueDefinition(name: EnumValueName(name: r'D')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), EnumDefinition(name: EnumName(name: r'_InputInputEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'_E')), EnumValueDefinition(name: EnumValueName(name: r'_F')), EnumValueDefinition(name: EnumValueName(name: r'_new')), EnumValueDefinition(name: EnumValueName(name: r'new')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_QueryResponse'), properties: [ ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_QueryResponse'), name: ClassPropertyName(name: r'q'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: TypeName(name: r'_InputInputEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: $InputInputEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'input_enum', isNonNull: true), name: QueryInputName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: InputEnum.artemisUnknown)' ]), QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'i')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$QueryResponse extends JsonSerializable with EquatableMixin { Custom$QueryRoot$QueryResponse(); factory Custom$QueryRoot$QueryResponse.fromJson(Map json) => _$Custom$QueryRoot$QueryResponseFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; @override List get props => [e]; @override Map toJson() => _$Custom$QueryRoot$QueryResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$QueryResponse? q; @override List get props => [q]; @override Map toJson() => _$Custom$QueryRootToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({this.e}); factory Input.fromJson(Map json) => _$InputFromJson(json); @JsonKey(unknownEnumValue: $InputInputEnum.artemisUnknown) $InputInputEnum? e; @override List get props => [e]; @override Map toJson() => _$InputToJson(this); } enum MyEnum { @JsonValue('A') a, @JsonValue('B') b, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } enum InputEnum { @JsonValue('C') c, @JsonValue('D') d, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } enum $InputInputEnum { @JsonValue('_E') $e, @JsonValue('_F') $f, @JsonValue('_new') $new, @JsonValue('new') kw$new, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/enums/input_enum_list_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On enums', () { test( 'List of enums as input', () async => testGenerator( query: query, schema: r''' schema { query: Query } type Query { articles(article_type_in: [ArticleType!]): [Article!] } type Article { id: ID! title: String! article_type: ArticleType! } enum ArticleType { NEWS TUTORIAL } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } const query = r''' query BrowseArticles($article_type_in: [ArticleType!]) { articles(article_type_in: $article_type_in) { id title article_type } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'BrowseArticles$_Query'), operationName: r'BrowseArticles', classes: [ EnumDefinition(name: EnumName(name: r'ArticleType'), values: [ EnumValueDefinition(name: EnumValueName(name: r'NEWS')), EnumValueDefinition(name: EnumValueName(name: r'TUTORIAL')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'BrowseArticles$_Query$_Article'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'title'), isResolveType: false), ClassProperty( type: TypeName(name: r'ArticleType', isNonNull: true), name: ClassPropertyName(name: r'article_type'), annotations: [ r'''JsonKey(name: 'article_type', unknownEnumValue: ArticleType.artemisUnknown)''' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'BrowseArticles$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'BrowseArticles$_Query$_Article', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'articles'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], inputs: [ QueryInput( type: ListOfTypeName( typeName: TypeName(name: r'ArticleType', isNonNull: true), isNonNull: false), name: QueryInputName(name: r'article_type_in'), annotations: [ r'JsonKey(unknownEnumValue: ArticleType.artemisUnknown)' ]) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class BrowseArticles$Query$Article extends JsonSerializable with EquatableMixin { BrowseArticles$Query$Article(); factory BrowseArticles$Query$Article.fromJson(Map json) => _$BrowseArticles$Query$ArticleFromJson(json); late String id; late String title; @JsonKey(name: 'article_type', unknownEnumValue: ArticleType.artemisUnknown) late ArticleType articleType; @override List get props => [id, title, articleType]; @override Map toJson() => _$BrowseArticles$Query$ArticleToJson(this); } @JsonSerializable(explicitToJson: true) class BrowseArticles$Query extends JsonSerializable with EquatableMixin { BrowseArticles$Query(); factory BrowseArticles$Query.fromJson(Map json) => _$BrowseArticles$QueryFromJson(json); List? articles; @override List get props => [articles]; @override Map toJson() => _$BrowseArticles$QueryToJson(this); } enum ArticleType { @JsonValue('NEWS') news, @JsonValue('TUTORIAL') tutorial, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } @JsonSerializable(explicitToJson: true) class BrowseArticlesArguments extends JsonSerializable with EquatableMixin { BrowseArticlesArguments({this.article_type_in}); @override factory BrowseArticlesArguments.fromJson(Map json) => _$BrowseArticlesArgumentsFromJson(json); @JsonKey(unknownEnumValue: ArticleType.artemisUnknown) final List? article_type_in; @override List get props => [article_type_in]; @override Map toJson() => _$BrowseArticlesArgumentsToJson(this); } final BROWSE_ARTICLES_QUERY_DOCUMENT_OPERATION_NAME = 'BrowseArticles'; final BROWSE_ARTICLES_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'BrowseArticles'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'article_type_in')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'ArticleType'), isNonNull: true, ), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'articles'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'article_type_in'), value: VariableNode(name: NameNode(value: 'article_type_in')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'title'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'article_type'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ) ]), ) ]); class BrowseArticlesQuery extends GraphQLQuery { BrowseArticlesQuery({required this.variables}); @override final DocumentNode document = BROWSE_ARTICLES_QUERY_DOCUMENT; @override final String operationName = BROWSE_ARTICLES_QUERY_DOCUMENT_OPERATION_NAME; @override final BrowseArticlesArguments variables; @override List get props => [document, operationName, variables]; @override BrowseArticles$Query parse(Map json) => BrowseArticles$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/enums/input_enum_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On enums', () { test( 'Enums can be part of input objects', () async => testGenerator( query: query, schema: r''' schema { query: QueryRoot } type QueryRoot { q(_id: ID!, input: Input!, o: OtherEnum!): QueryResponse } type QueryResponse { s: String my: MyEnum other: OtherEnum } input Input { e: MyEnum! } enum MyEnum { A B } enum OtherEnum { O1 O2 } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } const query = r''' query custom($_id: ID!, $input: Input!, $o: OtherEnum!) { q(_id: $_id, input: $input, o: $o) { s my other } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), EnumDefinition(name: EnumName(name: r'OtherEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'O1')), EnumValueDefinition(name: EnumValueName(name: r'O2')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_QueryResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'my'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false), ClassProperty( type: TypeName(name: r'OtherEnum'), name: ClassPropertyName(name: r'other'), annotations: [ r'JsonKey(unknownEnumValue: OtherEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_QueryResponse'), name: ClassPropertyName(name: r'q'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: TypeName(name: r'MyEnum', isNonNull: true), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'_id'), annotations: [r'''JsonKey(name: '_id')''']), QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')), QueryInput( type: TypeName(name: r'OtherEnum', isNonNull: true), name: QueryInputName(name: r'o'), annotations: [ r'JsonKey(unknownEnumValue: OtherEnum.artemisUnknown)' ]) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$QueryResponse extends JsonSerializable with EquatableMixin { Custom$QueryRoot$QueryResponse(); factory Custom$QueryRoot$QueryResponse.fromJson(Map json) => _$Custom$QueryRoot$QueryResponseFromJson(json); String? s; @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? my; @JsonKey(unknownEnumValue: OtherEnum.artemisUnknown) OtherEnum? other; @override List get props => [s, my, other]; @override Map toJson() => _$Custom$QueryRoot$QueryResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$QueryResponse? q; @override List get props => [q]; @override Map toJson() => _$Custom$QueryRootToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({required this.e}); factory Input.fromJson(Map json) => _$InputFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) late MyEnum e; @override List get props => [e]; @override Map toJson() => _$InputToJson(this); } enum MyEnum { @JsonValue('A') a, @JsonValue('B') b, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } enum OtherEnum { @JsonValue('O1') o1, @JsonValue('O2') o2, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } @JsonSerializable(explicitToJson: true) class CustomArguments extends JsonSerializable with EquatableMixin { CustomArguments({ required this.$id, required this.input, required this.o, }); @override factory CustomArguments.fromJson(Map json) => _$CustomArgumentsFromJson(json); @JsonKey(name: '_id') late String $id; late Input input; @JsonKey(unknownEnumValue: OtherEnum.artemisUnknown) late OtherEnum o; @override List get props => [$id, input, o]; @override Map toJson() => _$CustomArgumentsToJson(this); } final CUSTOM_QUERY_DOCUMENT_OPERATION_NAME = 'custom'; final CUSTOM_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'custom'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: '_id')), type: NamedTypeNode( name: NameNode(value: 'ID'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'o')), type: NamedTypeNode( name: NameNode(value: 'OtherEnum'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'q'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: '_id'), value: VariableNode(name: NameNode(value: '_id')), ), ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ), ArgumentNode( name: NameNode(value: 'o'), value: VariableNode(name: NameNode(value: 'o')), ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'my'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'other'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ) ]), ) ]); class CustomQuery extends GraphQLQuery { CustomQuery({required this.variables}); @override final DocumentNode document = CUSTOM_QUERY_DOCUMENT; @override final String operationName = CUSTOM_QUERY_DOCUMENT_OPERATION_NAME; @override final CustomArguments variables; @override List get props => [document, operationName, variables]; @override Custom$QueryRoot parse(Map json) => Custom$QueryRoot.fromJson(json); } '''; ================================================ FILE: test/query_generator/enums/kw_prefix_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On enums', () { test( 'Should correctly proceed enum fields with `kw` prefix', () async => testGenerator( query: query, schema: r''' type Query { articles(titleWhere: ArticleTitleWhereConditions): [Article!] } input ArticleTitleWhereConditions { operator: SQLOperator value: String } type Article { id: ID! title: String! } enum SQLOperator { EQ IN } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query SearchArticles($titleWhere: ArticleTitleWhereConditions) { articles(titleWhere: $titleWhere) { id title } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SearchArticles$_Query'), operationName: r'SearchArticles', classes: [ EnumDefinition(name: EnumName(name: r'SQLOperator'), values: [ EnumValueDefinition(name: EnumValueName(name: r'EQ')), EnumValueDefinition(name: EnumValueName(name: r'IN')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'SearchArticles$_Query$_Article'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'title'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SearchArticles$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'SearchArticles$_Query$_Article', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'articles'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'ArticleTitleWhereConditions'), properties: [ ClassProperty( type: TypeName(name: r'SQLOperator'), name: ClassPropertyName(name: r'operator'), annotations: [ r'''JsonKey(name: 'operator', unknownEnumValue: SQLOperator.artemisUnknown)''' ], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'value'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'ArticleTitleWhereConditions'), name: QueryInputName(name: r'titleWhere')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SearchArticles$Query$Article extends JsonSerializable with EquatableMixin { SearchArticles$Query$Article(); factory SearchArticles$Query$Article.fromJson(Map json) => _$SearchArticles$Query$ArticleFromJson(json); late String id; late String title; @override List get props => [id, title]; @override Map toJson() => _$SearchArticles$Query$ArticleToJson(this); } @JsonSerializable(explicitToJson: true) class SearchArticles$Query extends JsonSerializable with EquatableMixin { SearchArticles$Query(); factory SearchArticles$Query.fromJson(Map json) => _$SearchArticles$QueryFromJson(json); List? articles; @override List get props => [articles]; @override Map toJson() => _$SearchArticles$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class ArticleTitleWhereConditions extends JsonSerializable with EquatableMixin { ArticleTitleWhereConditions({ this.kw$operator, this.value, }); factory ArticleTitleWhereConditions.fromJson(Map json) => _$ArticleTitleWhereConditionsFromJson(json); @JsonKey(name: 'operator', unknownEnumValue: SQLOperator.artemisUnknown) SQLOperator? kw$operator; String? value; @override List get props => [kw$operator, value]; @override Map toJson() => _$ArticleTitleWhereConditionsToJson(this); } enum SQLOperator { @JsonValue('EQ') eq, @JsonValue('IN') kw$IN, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/enums/query_enum_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On enums', () { test( 'Enums can be part of queries responses', () async => testGenerator( query: query, schema: r''' schema { query: QueryRoot } type QueryRoot { q: QueryResponse } type QueryResponse { e: MyEnum } enum MyEnum { A B IN } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query custom { q { e } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'A')), EnumValueDefinition(name: EnumValueName(name: r'B')), EnumValueDefinition(name: EnumValueName(name: r'IN')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot$_QueryResponse'), properties: [ ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_QueryRoot$_QueryResponse'), name: ClassPropertyName(name: r'q'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot$QueryResponse extends JsonSerializable with EquatableMixin { Custom$QueryRoot$QueryResponse(); factory Custom$QueryRoot$QueryResponse.fromJson(Map json) => _$Custom$QueryRoot$QueryResponseFromJson(json); @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; @override List get props => [e]; @override Map toJson() => _$Custom$QueryRoot$QueryResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); Custom$QueryRoot$QueryResponse? q; @override List get props => [q]; @override Map toJson() => _$Custom$QueryRootToJson(this); } enum MyEnum { @JsonValue('A') a, @JsonValue('B') b, @JsonValue('IN') kw$IN, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/errors/fragment_not_found_test.dart ================================================ import 'package:artemis/builder.dart'; import 'package:artemis/generator/errors.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:test/test.dart'; void main() { group('On errors', () { test('When there\'s a missing fragment being used', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'queries_glob': 'lib/queries/some_query.graphql', 'output': 'lib/output/some_query.graphql.dart', }, ], })); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' type Query { a: String! } ''', 'a|lib/queries/some_query.graphql': 'query { ...nonExistentFragment }', }, onLog: print, ), throwsA(predicate((e) => e is MissingFragmentException && e.fragmentName == 'NonExistentFragmentMixin' && e.className == r'SomeQuery$Query')), ); }); }); } ================================================ FILE: test/query_generator/errors/generation_errors_test.dart ================================================ import 'package:artemis/builder.dart'; import 'package:artemis/generator/errors.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:test/test.dart'; void main() { group('On errors', () { test('When the schema glob matches queries glob', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'non_existent_api.schema.graphql', 'queries_glob': '**.graphql', 'output': 'lib/some_query.dart', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.json': '', 'a|api.schema.grqphql': '', 'a|some_query.query.graphql': 'query some_query { s }', }, onLog: print), throwsA(predicate((e) => e is QueryGlobsSchemaException))); }); test("When user hasn't configured an output", () async { expect( () { graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.grqphql', 'queries_glob': 'queries/**.graphql', }, ], })); }, throwsA(predicate((e) { return e is MissingBuildConfigurationException && e.name == 'schema_mapping => output'; })), ); }); test("When user hasn't configured an queries_glob", () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.grqphql', 'output': 'lib/some_query.dart', }, ], })); try { await testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' ''', 'a|queries/query.graphql': ''' ''', 'a|fragment.frag': ''' ''' }, onLog: print); } on MissingBuildConfigurationException catch (e) { expect(e.name, 'schema_map => queries_glob'); return; } throw Exception('Expected MissingBuildConfigurationException'); }); test('When user fragments_glob return empty file', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'fragments_glob': '**.frag', 'schema_mapping': [ { 'schema': 'api.schema.grqphql', 'output': 'lib/some_query.dart', }, ], })); try { await testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' ''', 'a|queries/query.graphql': ''' ''', }, onLog: print); } on MissingFilesException catch (e) { expect(e.globPattern, '**.frag'); return; } throw Exception('Expected MissingFilesException'); }); test('When user fragments_glob at schema level return empty file', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.grqphql', 'fragments_glob': '**.schema', 'output': 'lib/some_query.dart', }, ], })); try { await testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' ''', 'a|queries/query.graphql': ''' ''', }, onLog: print); } on MissingFilesException catch (e) { expect(e.globPattern, '**.schema'); return; } throw Exception('Expected MissingFilesException'); }); test('When schema_mapping is empty', () async { try { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [], })); await testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' ''', 'a|queries/query.graphql': ''' ''', }, onLog: print); } on MissingBuildConfigurationException catch (e) { expect(e.name, 'schema_mapping'); return; } throw Exception('Expected MissingBuildConfigurationException'); }); test('When schema_mapping => schema is not defined', () async { try { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'queries_glob': '**.graphql', 'output': 'lib/some_query.dart', } ], })); await testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' ''', 'a|queries/query.graphql': ''' ''', }, onLog: print); } on MissingBuildConfigurationException catch (e) { expect(e.name, 'schema_map => schema'); return; } throw Exception('Expected MissingBuildConfigurationException'); }); // test('When fragments_glob meets local fragments', () async { // final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ // 'generate_helpers': false, // 'schema_mapping': [ // { // 'schema': 'api.schema.graphql', // 'output': 'lib/some_query.dart', // 'queries_glob': 'queries/**.graphql', // }, // ], // 'fragments_glob': '**.frag', // })); // // anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); // // expect( // () => testBuilder( // anotherBuilder, // { // 'a|api.schema.graphql': ''' // schema { // query: Query // } // // type Query { // pokemon: Pokemon // } // // type Pokemon { // id: String! // } // ''', // 'a|queries/query.graphql': ''' // { // pokemon { // ...Pokemon // } // } // // fragment Pokemon on Pokemon { // id // } // ''', // 'a|fragment.frag': '''fragment Pokemon on Pokemon { // id // }''' // }, // onLog: print), // throwsA(predicate((e) => e is FragmentIgnoreException))); // }); test('When the schema file is not found', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'non_existent_api.schema.graphql', 'queries_glob': 'lib/**.graphql', 'output': 'lib/some_query.dart', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.json': '', 'a|api.schema.grqphql': '', 'a|some_query.query.graphql': 'query some_query { s }', }, onLog: print), throwsA(predicate( (e) => e is MissingFilesException && e.globPattern == 'non_existent_api.schema.graphql', ))); }); test('When the queries_glob files are not found', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.grqphql', 'queries_glob': 'lib/**.graphql', 'output': 'lib/some_query.dart', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.grqphql': '', 'a|some_query.query.graphql': 'query some_query { s }', }, onLog: print), throwsA(predicate( (e) => e is MissingFilesException && e.globPattern == 'lib/**.graphql', ))); }); }); test('Fragments with same name but with different selection set', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'output': 'lib/some_query.dart', 'queries_glob': 'queries/**.graphql', 'naming_scheme': 'pathedWithFields', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.graphql': ''' schema { query: Query } type Query { pokemon: Pokemon } type Pokemon { id: String! name: String! } ''', 'a|queries/query.graphql': ''' { pokemon { ...Pokemon } } fragment Pokemon on Pokemon { id } ''', 'a|queries/anotherQuery.graphql': ''' { pokemon { ...Pokemon } } fragment Pokemon on Pokemon { id name } ''', }, onLog: print), throwsA(predicate((e) => e is DuplicatedClassesException))); }); test('When the query globs schema location', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'lib/schema.graphql', 'queries_glob': 'lib/*.graphql', 'output': 'lib/output/some_query.dart', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.json': '', 'a|api.schema.grqphql': '', 'a|some_query.query.graphql': 'query some_query { s }', }, onLog: print), throwsA(predicate((e) => e is QueryGlobsSchemaException))); }); test('When the query globs output location', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'schema.graphql', 'queries_glob': 'lib/*', 'output': 'lib/output.dart', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.json': '', 'a|api.schema.grqphql': '', 'a|some_query.query.graphql': 'query some_query { s }', }, onLog: print), throwsA(predicate((e) => e is QueryGlobsOutputException))); }); test('When scalar_mapping does not define a custom scalar', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'output': 'lib/output/some_query.dart', 'queries_glob': 'lib/queries/some_query.graphql', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|api.schema.graphql': r''' scalar DateTime type Query { s: DateTime } ''', 'a|lib/queries/some_query.graphql': 'query some_query { s }', }, onLog: print), throwsA(predicate((e) => e is MissingScalarConfigurationException && e.scalarName == 'DateTime'))); }); } ================================================ FILE: test/query_generator/errors/root_type_not_found_test.dart ================================================ import 'package:artemis/builder.dart'; import 'package:artemis/generator/errors.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:test/test.dart'; void main() { group('On errors', () { test('When there\'s no root type on schema', () async { final anotherBuilder = graphQLQueryBuilder(BuilderOptions({ 'generate_helpers': false, 'schema_mapping': [ { 'schema': 'lib/api.schema.graphql', 'queries_glob': 'lib/**.query.graphql', 'output': 'lib/some_query.graphql.dart', }, ], })); anotherBuilder.onBuild = expectAsync1((_) {}, count: 0); expect( () => testBuilder( anotherBuilder, { 'a|lib/api.schema.graphql': '', 'a|lib/some.query.graphql': 'query { a }', }, onLog: print, ), throwsA(predicate((e) => e is MissingRootTypeException)), ); }); }); } ================================================ FILE: test/query_generator/forwarder_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On forwarder', () { test( 'Forwarder are created if output file does not end with .graphql.dart', () async => testGenerator( query: query, libraryDefinition: libraryDefinition, generatedFile: generatedFile, schema: r''' schema { query: QueryRoot } type QueryRoot { a: String }''', builderOptionsMap: { 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'queries_glob': 'queries/**.graphql', 'output': 'lib/query.dart', } ], }, outputsMap: { 'a|lib/query.graphql.dart': generatedFile, 'a|lib/query.dart': r'''// GENERATED CODE - DO NOT MODIFY BY HAND export 'query.graphql.dart'; ''', }, ), ); }); } const query = r''' query custom { a } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_QueryRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_QueryRoot'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'a'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$QueryRoot extends JsonSerializable with EquatableMixin { Custom$QueryRoot(); factory Custom$QueryRoot.fromJson(Map json) => _$Custom$QueryRootFromJson(json); String? a; @override List get props => [a]; @override Map toJson() => _$Custom$QueryRootToJson(this); } '''; ================================================ FILE: test/query_generator/fragments/fragment_duplication_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('Fragment duplication', () { test( 'Fragment duplication should be properly handeled', () async => testGenerator( namingScheme: 'pathedWithFields', query: queryString, schema: r''' schema { query: Query } type Query { pokemon(id: String, name: String): Pokemon allPokemons: [Pokemon] } type Pokemon { id: String! name: String number: String evolution: Pokemon } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: {'fragments_glob': '**.frag'}, sourceAssetsMap: { 'a|fragment.frag': fragmentsString, 'a|queries/another_query.graphql': anotherQueryString, }, ), ); }); } const fragmentsString = ''' fragment PokemonParts on Pokemon { number name } fragment PokemonName on Pokemon { name } fragment Pokemon on Pokemon { id ...PokemonParts evolution { ...PokemonName } } '''; const queryString = ''' query PokemonData { pokemon(name: "Pikachu") { ...Pokemon } } '''; const anotherQueryString = ''' query AllPokemonsData { allPokemons { ...Pokemon } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'PokemonData$_Query'), operationName: r'PokemonData', classes: [ ClassDefinition( name: ClassName(name: r'PokemonData$_Query$_pokemon'), mixins: [ FragmentName(name: r'PokemonMixin'), FragmentName(name: r'PokemonPartsMixin') ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonData$_Query'), properties: [ ClassProperty( type: TypeName(name: r'PokemonData$_Query$_pokemon'), name: ClassPropertyName(name: r'pokemon'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_evolution'), mixins: [FragmentName(name: r'PokemonNameMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_evolution'), name: ClassPropertyName(name: r'evolution'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'PokemonNameMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'PokemonPartsMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'number'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], generateHelpers: false, suffix: r'Query'), QueryDefinition( name: QueryName(name: r'AllPokemonsData$_Query'), operationName: r'AllPokemonsData', classes: [ ClassDefinition( name: ClassName(name: r'AllPokemonsData$_Query$_allPokemons'), mixins: [ FragmentName(name: r'PokemonMixin'), FragmentName(name: r'PokemonPartsMixin') ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'AllPokemonsData$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'AllPokemonsData$_Query$_allPokemons'), isNonNull: false), name: ClassPropertyName(name: r'allPokemons'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_evolution'), mixins: [FragmentName(name: r'PokemonNameMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_evolution'), name: ClassPropertyName(name: r'evolution'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'PokemonNameMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'PokemonPartsMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'number'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin PokemonMixin { late String id; PokemonMixin$Evolution? evolution; } mixin PokemonNameMixin { String? name; } mixin PokemonPartsMixin { String? number; String? name; } @JsonSerializable(explicitToJson: true) class PokemonData$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin, PokemonPartsMixin { PokemonData$Query$Pokemon(); factory PokemonData$Query$Pokemon.fromJson(Map json) => _$PokemonData$Query$PokemonFromJson(json); @override List get props => [id, evolution, number, name]; @override Map toJson() => _$PokemonData$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonData$Query extends JsonSerializable with EquatableMixin { PokemonData$Query(); factory PokemonData$Query.fromJson(Map json) => _$PokemonData$QueryFromJson(json); PokemonData$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$PokemonData$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$Evolution extends JsonSerializable with EquatableMixin, PokemonNameMixin { PokemonMixin$Evolution(); factory PokemonMixin$Evolution.fromJson(Map json) => _$PokemonMixin$EvolutionFromJson(json); @override List get props => [name]; @override Map toJson() => _$PokemonMixin$EvolutionToJson(this); } @JsonSerializable(explicitToJson: true) class AllPokemonsData$Query$AllPokemons extends JsonSerializable with EquatableMixin, PokemonMixin, PokemonPartsMixin { AllPokemonsData$Query$AllPokemons(); factory AllPokemonsData$Query$AllPokemons.fromJson( Map json) => _$AllPokemonsData$Query$AllPokemonsFromJson(json); @override List get props => [id, evolution, number, name]; @override Map toJson() => _$AllPokemonsData$Query$AllPokemonsToJson(this); } @JsonSerializable(explicitToJson: true) class AllPokemonsData$Query extends JsonSerializable with EquatableMixin { AllPokemonsData$Query(); factory AllPokemonsData$Query.fromJson(Map json) => _$AllPokemonsData$QueryFromJson(json); List? allPokemons; @override List get props => [allPokemons]; @override Map toJson() => _$AllPokemonsData$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/fragments/fragment_glob_schema_level_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('[Fragment generation using schema level glob]', () { test( 'Extracting', () async => testGenerator( query: queryString, schema: r''' schema { query: Query } type Query { pokemon(id: String, name: String): Pokemon } type Pokemon { id: String! name: String evolutions: [Pokemon] attacks: PokemonAttack weight: PokemonDimension } type PokemonAttack { special: [Attack] } type PokemonDimension { minimum: String } type Attack { name: String } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: { 'fragments_glob': '**.frag', 'schema_mapping': [ { 'schema': 'api.schema.graphql', 'queries_glob': 'queries/**.graphql', 'fragments_glob': '**.schema', 'output': 'lib/query.graphql.dart', } ], }, sourceAssetsMap: { 'a|fragment.frag': fragmentsString, 'a|fragment.schema': fragmentsSchemaLevelString, }, generateHelpers: true, ), ); }); } const fragmentsString = ''' fragment Pokemon on Pokemon { id weight { ...weight } attacks { ...pokemonAttack } } fragment weight on PokemonDimension { minimum } '''; const fragmentsSchemaLevelString = ''' fragment pokemonAttack on PokemonAttack { special { ...attack } } fragment attack on Attack { name } '''; const queryString = ''' { pokemon(name: "Pikachu") { ...Pokemon evolutions { ...Pokemon } } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_Query'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_Query$_Pokemon$_Pokemon'), mixins: [FragmentName(name: r'PokemonMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Query$_Query$_Pokemon'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'Query$_Query$_Pokemon$_Pokemon'), isNonNull: false), name: ClassPropertyName(name: r'evolutions'), isResolveType: false) ], mixins: [FragmentName(name: r'PokemonMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Query$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Query$_Query$_Pokemon'), name: ClassPropertyName(name: r'pokemon'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_PokemonDimension'), mixins: [FragmentName(name: r'WeightMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_PokemonAttack'), mixins: [FragmentName(name: r'PokemonAttackMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_PokemonDimension'), name: ClassPropertyName(name: r'weight'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_PokemonAttack'), name: ClassPropertyName(name: r'attacks'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'WeightMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'minimum'), isResolveType: false) ]), ClassDefinition( name: ClassName(name: r'PokemonAttackMixin$_Attack'), mixins: [FragmentName(name: r'AttackMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonAttackMixin'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'PokemonAttackMixin$_Attack'), isNonNull: false), name: ClassPropertyName(name: r'special'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'AttackMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin PokemonMixin { late String id; PokemonMixin$PokemonDimension? weight; PokemonMixin$PokemonAttack? attacks; } mixin WeightMixin { String? minimum; } mixin PokemonAttackMixin { List? special; } mixin AttackMixin { String? name; } @JsonSerializable(explicitToJson: true) class Query$Query$Pokemon$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin { Query$Query$Pokemon$Pokemon(); factory Query$Query$Pokemon$Pokemon.fromJson(Map json) => _$Query$Query$Pokemon$PokemonFromJson(json); @override List get props => [id, weight, attacks]; @override Map toJson() => _$Query$Query$Pokemon$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class Query$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin { Query$Query$Pokemon(); factory Query$Query$Pokemon.fromJson(Map json) => _$Query$Query$PokemonFromJson(json); List? evolutions; @override List get props => [id, weight, attacks, evolutions]; @override Map toJson() => _$Query$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class Query$Query extends JsonSerializable with EquatableMixin { Query$Query(); factory Query$Query.fromJson(Map json) => _$Query$QueryFromJson(json); Query$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$Query$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$PokemonDimension extends JsonSerializable with EquatableMixin, WeightMixin { PokemonMixin$PokemonDimension(); factory PokemonMixin$PokemonDimension.fromJson(Map json) => _$PokemonMixin$PokemonDimensionFromJson(json); @override List get props => [minimum]; @override Map toJson() => _$PokemonMixin$PokemonDimensionToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$PokemonAttack extends JsonSerializable with EquatableMixin, PokemonAttackMixin { PokemonMixin$PokemonAttack(); factory PokemonMixin$PokemonAttack.fromJson(Map json) => _$PokemonMixin$PokemonAttackFromJson(json); @override List get props => [special]; @override Map toJson() => _$PokemonMixin$PokemonAttackToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonAttackMixin$Attack extends JsonSerializable with EquatableMixin, AttackMixin { PokemonAttackMixin$Attack(); factory PokemonAttackMixin$Attack.fromJson(Map json) => _$PokemonAttackMixin$AttackFromJson(json); @override List get props => [name]; @override Map toJson() => _$PokemonAttackMixin$AttackToJson(this); } final QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'query'; final QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: null, variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: StringValueNode( value: 'Pikachu', isBlock: false, ), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Pokemon'), directives: [], ), FieldNode( name: NameNode(value: 'evolutions'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Pokemon'), directives: [], ) ]), ), ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'Pokemon'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Pokemon'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'weight'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'weight'), directives: [], ) ]), ), FieldNode( name: NameNode(value: 'attacks'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'pokemonAttack'), directives: [], ) ]), ), ]), ), FragmentDefinitionNode( name: NameNode(value: 'weight'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'PokemonDimension'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'minimum'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'pokemonAttack'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'PokemonAttack'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'special'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'attack'), directives: [], ) ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'attack'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Attack'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ), ]); class QueryQuery extends GraphQLQuery { QueryQuery(); @override final DocumentNode document = QUERY_QUERY_DOCUMENT; @override final String operationName = QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override List get props => [document, operationName]; @override Query$Query parse(Map json) => Query$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/fragments/fragment_glob_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('[Fragment generation]', () { test( 'Extracting', () async => testGenerator( query: queryString, schema: r''' schema { query: Query } type Query { pokemon(id: String, name: String): Pokemon } type Pokemon { id: String! name: String evolutions: [Pokemon] attacks: PokemonAttack weight: PokemonDimension } type PokemonAttack { special: [Attack] } type PokemonDimension { minimum: String } type Attack { name: String } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: {'fragments_glob': '**.frag'}, sourceAssetsMap: {'a|fragment.frag': fragmentsString}, generateHelpers: true, ), ); }); } const fragmentsString = ''' fragment Pokemon on Pokemon { id weight { ...weight } attacks { ...pokemonAttack } } fragment weight on PokemonDimension { minimum } fragment pokemonAttack on PokemonAttack { special { ...attack } } fragment attack on Attack { name } '''; const queryString = ''' { pokemon(name: "Pikachu") { ...Pokemon evolutions { ...Pokemon } } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_Query'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_Query$_Pokemon$_Pokemon'), mixins: [FragmentName(name: r'PokemonMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Query$_Query$_Pokemon'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'Query$_Query$_Pokemon$_Pokemon'), isNonNull: false), name: ClassPropertyName(name: r'evolutions'), isResolveType: false) ], mixins: [FragmentName(name: r'PokemonMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Query$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Query$_Query$_Pokemon'), name: ClassPropertyName(name: r'pokemon'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_PokemonDimension'), mixins: [FragmentName(name: r'WeightMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_PokemonAttack'), mixins: [FragmentName(name: r'PokemonAttackMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_PokemonDimension'), name: ClassPropertyName(name: r'weight'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_PokemonAttack'), name: ClassPropertyName(name: r'attacks'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'WeightMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'minimum'), isResolveType: false) ]), ClassDefinition( name: ClassName(name: r'PokemonAttackMixin$_Attack'), mixins: [FragmentName(name: r'AttackMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonAttackMixin'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'PokemonAttackMixin$_Attack'), isNonNull: false), name: ClassPropertyName(name: r'special'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'AttackMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin PokemonMixin { late String id; PokemonMixin$PokemonDimension? weight; PokemonMixin$PokemonAttack? attacks; } mixin WeightMixin { String? minimum; } mixin PokemonAttackMixin { List? special; } mixin AttackMixin { String? name; } @JsonSerializable(explicitToJson: true) class Query$Query$Pokemon$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin { Query$Query$Pokemon$Pokemon(); factory Query$Query$Pokemon$Pokemon.fromJson(Map json) => _$Query$Query$Pokemon$PokemonFromJson(json); @override List get props => [id, weight, attacks]; @override Map toJson() => _$Query$Query$Pokemon$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class Query$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin { Query$Query$Pokemon(); factory Query$Query$Pokemon.fromJson(Map json) => _$Query$Query$PokemonFromJson(json); List? evolutions; @override List get props => [id, weight, attacks, evolutions]; @override Map toJson() => _$Query$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class Query$Query extends JsonSerializable with EquatableMixin { Query$Query(); factory Query$Query.fromJson(Map json) => _$Query$QueryFromJson(json); Query$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$Query$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$PokemonDimension extends JsonSerializable with EquatableMixin, WeightMixin { PokemonMixin$PokemonDimension(); factory PokemonMixin$PokemonDimension.fromJson(Map json) => _$PokemonMixin$PokemonDimensionFromJson(json); @override List get props => [minimum]; @override Map toJson() => _$PokemonMixin$PokemonDimensionToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$PokemonAttack extends JsonSerializable with EquatableMixin, PokemonAttackMixin { PokemonMixin$PokemonAttack(); factory PokemonMixin$PokemonAttack.fromJson(Map json) => _$PokemonMixin$PokemonAttackFromJson(json); @override List get props => [special]; @override Map toJson() => _$PokemonMixin$PokemonAttackToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonAttackMixin$Attack extends JsonSerializable with EquatableMixin, AttackMixin { PokemonAttackMixin$Attack(); factory PokemonAttackMixin$Attack.fromJson(Map json) => _$PokemonAttackMixin$AttackFromJson(json); @override List get props => [name]; @override Map toJson() => _$PokemonAttackMixin$AttackToJson(this); } final QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'query'; final QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: null, variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: StringValueNode( value: 'Pikachu', isBlock: false, ), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Pokemon'), directives: [], ), FieldNode( name: NameNode(value: 'evolutions'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Pokemon'), directives: [], ) ]), ), ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'Pokemon'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Pokemon'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'weight'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'weight'), directives: [], ) ]), ), FieldNode( name: NameNode(value: 'attacks'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'pokemonAttack'), directives: [], ) ]), ), ]), ), FragmentDefinitionNode( name: NameNode(value: 'weight'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'PokemonDimension'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'minimum'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'pokemonAttack'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'PokemonAttack'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'special'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'attack'), directives: [], ) ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'attack'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Attack'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ), ]); class QueryQuery extends GraphQLQuery { QueryQuery(); @override final DocumentNode document = QUERY_QUERY_DOCUMENT; @override final String operationName = QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override List get props => [document, operationName]; @override Query$Query parse(Map json) => Query$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/fragments/fragment_multiple_queries_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On fragments', () { test( 'One fragment with multiple queries per file', () async => testGenerator( query: r''' query getPokemon($name: String!) { pokemon(name: $name) { ...pokemonFragment } } query getAllPokemons($first: Int!) { pokemons(first: $first) { ...pokemonFragment } } ''', schema: r''' schema { query: Query } type Attack { name: String type: String damage: Int } type Pokemon { id: ID! number: String name: String weight: PokemonDimension height: PokemonDimension classification: String types: [String] resistant: [String] attacks: PokemonAttack weaknesses: [String] fleeRate: Float maxCP: Int evolutions: [Pokemon] evolutionRequirements: PokemonEvolutionRequirement maxHP: Int image: String } type PokemonAttack { fast: [Attack] special: [Attack] } type PokemonDimension { minimum: String maximum: String } type PokemonEvolutionRequirement { amount: Int name: String } type Query { pokemons(first: Int!): [Pokemon] pokemon(id: String, name: String): Pokemon } ''', builderOptionsMap: {'fragments_glob': '**.frag'}, sourceAssetsMap: {'a|fragment.frag': fragmentsString}, libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } const fragmentsString = ''' fragment pokemonFragment on Pokemon { number name } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'GetPokemon$_Query'), operationName: r'getPokemon', classes: [ ClassDefinition( name: ClassName(name: r'GetPokemon$_Query$_Pokemon'), mixins: [FragmentName(name: r'PokemonFragmentMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'GetPokemon$_Query'), properties: [ ClassProperty( type: TypeName(name: r'GetPokemon$_Query$_Pokemon'), name: ClassPropertyName(name: r'pokemon'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonFragmentMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'number'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'name')) ], generateHelpers: true, suffix: r'Query'), QueryDefinition( name: QueryName(name: r'GetAllPokemons$_Query'), operationName: r'getAllPokemons', classes: [ ClassDefinition( name: ClassName(name: r'GetAllPokemons$_Query$_Pokemon'), mixins: [FragmentName(name: r'PokemonFragmentMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'GetAllPokemons$_Query'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'GetAllPokemons$_Query$_Pokemon'), isNonNull: false), name: ClassPropertyName(name: r'pokemons'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonFragmentMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'number'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], inputs: [ QueryInput( type: DartTypeName(name: r'int', isNonNull: true), name: QueryInputName(name: r'first')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin PokemonFragmentMixin { String? number; String? name; } @JsonSerializable(explicitToJson: true) class GetPokemon$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonFragmentMixin { GetPokemon$Query$Pokemon(); factory GetPokemon$Query$Pokemon.fromJson(Map json) => _$GetPokemon$Query$PokemonFromJson(json); @override List get props => [number, name]; @override Map toJson() => _$GetPokemon$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class GetPokemon$Query extends JsonSerializable with EquatableMixin { GetPokemon$Query(); factory GetPokemon$Query.fromJson(Map json) => _$GetPokemon$QueryFromJson(json); GetPokemon$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$GetPokemon$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class GetAllPokemons$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonFragmentMixin { GetAllPokemons$Query$Pokemon(); factory GetAllPokemons$Query$Pokemon.fromJson(Map json) => _$GetAllPokemons$Query$PokemonFromJson(json); @override List get props => [number, name]; @override Map toJson() => _$GetAllPokemons$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class GetAllPokemons$Query extends JsonSerializable with EquatableMixin { GetAllPokemons$Query(); factory GetAllPokemons$Query.fromJson(Map json) => _$GetAllPokemons$QueryFromJson(json); List? pokemons; @override List get props => [pokemons]; @override Map toJson() => _$GetAllPokemons$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class GetPokemonArguments extends JsonSerializable with EquatableMixin { GetPokemonArguments({required this.name}); @override factory GetPokemonArguments.fromJson(Map json) => _$GetPokemonArgumentsFromJson(json); late String name; @override List get props => [name]; @override Map toJson() => _$GetPokemonArgumentsToJson(this); } final GET_POKEMON_QUERY_DOCUMENT_OPERATION_NAME = 'getPokemon'; final GET_POKEMON_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'getPokemon'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'name')), type: NamedTypeNode( name: NameNode(value: 'String'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemon'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'name'), value: VariableNode(name: NameNode(value: 'name')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'pokemonFragment'), directives: [], ) ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'pokemonFragment'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Pokemon'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ), ]); class GetPokemonQuery extends GraphQLQuery { GetPokemonQuery({required this.variables}); @override final DocumentNode document = GET_POKEMON_QUERY_DOCUMENT; @override final String operationName = GET_POKEMON_QUERY_DOCUMENT_OPERATION_NAME; @override final GetPokemonArguments variables; @override List get props => [document, operationName, variables]; @override GetPokemon$Query parse(Map json) => GetPokemon$Query.fromJson(json); } @JsonSerializable(explicitToJson: true) class GetAllPokemonsArguments extends JsonSerializable with EquatableMixin { GetAllPokemonsArguments({required this.first}); @override factory GetAllPokemonsArguments.fromJson(Map json) => _$GetAllPokemonsArgumentsFromJson(json); late int first; @override List get props => [first]; @override Map toJson() => _$GetAllPokemonsArgumentsToJson(this); } final GET_ALL_POKEMONS_QUERY_DOCUMENT_OPERATION_NAME = 'getAllPokemons'; final GET_ALL_POKEMONS_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'getAllPokemons'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'first')), type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'pokemons'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'first'), value: VariableNode(name: NameNode(value: 'first')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'pokemonFragment'), directives: [], ) ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'pokemonFragment'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Pokemon'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'number'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ), ]); class GetAllPokemonsQuery extends GraphQLQuery { GetAllPokemonsQuery({required this.variables}); @override final DocumentNode document = GET_ALL_POKEMONS_QUERY_DOCUMENT; @override final String operationName = GET_ALL_POKEMONS_QUERY_DOCUMENT_OPERATION_NAME; @override final GetAllPokemonsArguments variables; @override List get props => [document, operationName, variables]; @override GetAllPokemons$Query parse(Map json) => GetAllPokemons$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/fragments/fragment_on_fragments_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On fragment spreads on other fragments', () { test( 'Properties will be merged', () async => testGenerator( query: queryString, schema: r''' schema { query: Query } type Query { pokemon(id: String, name: String): Pokemon } type Pokemon { id: String! name: String number: String evolution: Pokemon } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: {'fragments_glob': '**.frag'}, sourceAssetsMap: {'a|fragment.frag': fragmentsString}, ), ); }); } const fragmentsString = ''' fragment PokemonParts on Pokemon { number name } fragment PokemonName on Pokemon { name } fragment Pokemon on Pokemon { id ...PokemonParts evolution { ...PokemonName } } '''; const queryString = ''' { pokemon(name: "Pikachu") { ...Pokemon } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_Query'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_Query$_Pokemon'), mixins: [ FragmentName(name: r'PokemonMixin'), FragmentName(name: r'PokemonPartsMixin') ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Query$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Query$_Query$_Pokemon'), name: ClassPropertyName(name: r'pokemon'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'PokemonMixin$_Pokemon'), mixins: [FragmentName(name: r'PokemonNameMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'PokemonMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'PokemonMixin$_Pokemon'), name: ClassPropertyName(name: r'evolution'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'PokemonNameMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'PokemonPartsMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'number'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'name'), isResolveType: false) ]) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin PokemonMixin { late String id; PokemonMixin$Pokemon? evolution; } mixin PokemonNameMixin { String? name; } mixin PokemonPartsMixin { String? number; String? name; } @JsonSerializable(explicitToJson: true) class Query$Query$Pokemon extends JsonSerializable with EquatableMixin, PokemonMixin, PokemonPartsMixin { Query$Query$Pokemon(); factory Query$Query$Pokemon.fromJson(Map json) => _$Query$Query$PokemonFromJson(json); @override List get props => [id, evolution, number, name]; @override Map toJson() => _$Query$Query$PokemonToJson(this); } @JsonSerializable(explicitToJson: true) class Query$Query extends JsonSerializable with EquatableMixin { Query$Query(); factory Query$Query.fromJson(Map json) => _$Query$QueryFromJson(json); Query$Query$Pokemon? pokemon; @override List get props => [pokemon]; @override Map toJson() => _$Query$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class PokemonMixin$Pokemon extends JsonSerializable with EquatableMixin, PokemonNameMixin { PokemonMixin$Pokemon(); factory PokemonMixin$Pokemon.fromJson(Map json) => _$PokemonMixin$PokemonFromJson(json); @override List get props => [name]; @override Map toJson() => _$PokemonMixin$PokemonToJson(this); } '''; ================================================ FILE: test/query_generator/fragments/fragments_multiple_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On fragments multiple', () { test( 'Fragments will have their own classes multiple', () async => testGenerator( namingScheme: 'pathedWithFields', query: r''' fragment Dst on Destination { id name } fragment Departure on Destination { id } query VoyagesData($input: PaginationInput!) { voyages(pagination: $input) { voyages { numberOfReports voyage { dateFrom dateTo id voyageNumber arrival { ...Dst } departure { ...Departure } } } } } ''', schema: r''' schema { query: Query } scalar DateTime type Query { voyages(pagination: PaginationInput!): VoyageList! } type VoyageList { voyages: [VoyageDetails!]! } type VoyageDetails { numberOfReports: Int! voyage: Voyage! } type Voyage { arrival: Destination! dateFrom: DateTime! dateTo: DateTime departure: Destination! visitPoint: Destination! id: ID voyageNumber: String! } type Destination { id: ID! name: String! } input PaginationInput { limit: Int! offset: Int! } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, builderOptionsMap: { 'scalar_mapping': [ { 'graphql_type': 'DateTime', 'dart_type': 'DateTime', }, ], }, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'VoyagesData$_Query'), operationName: r'VoyagesData', classes: [ ClassDefinition( name: ClassName( name: r'VoyagesData$_Query$_voyages$_voyages$_voyage$_arrival'), mixins: [FragmentName(name: r'DstMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName( name: r'VoyagesData$_Query$_voyages$_voyages$_voyage$_departure'), mixins: [FragmentName(name: r'DepartureMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName( name: r'VoyagesData$_Query$_voyages$_voyages$_voyage'), properties: [ ClassProperty( type: DartTypeName(name: r'DateTime', isNonNull: true), name: ClassPropertyName(name: r'dateFrom'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'DateTime'), name: ClassPropertyName(name: r'dateTo'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'voyageNumber'), isResolveType: false), ClassProperty( type: TypeName( name: r'VoyagesData$_Query$_voyages$_voyages$_voyage$_arrival', isNonNull: true), name: ClassPropertyName(name: r'arrival'), isResolveType: false), ClassProperty( type: TypeName( name: r'VoyagesData$_Query$_voyages$_voyages$_voyage$_departure', isNonNull: true), name: ClassPropertyName(name: r'departure'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'VoyagesData$_Query$_voyages$_voyages'), properties: [ ClassProperty( type: DartTypeName(name: r'int', isNonNull: true), name: ClassPropertyName(name: r'numberOfReports'), isResolveType: false), ClassProperty( type: TypeName( name: r'VoyagesData$_Query$_voyages$_voyages$_voyage', isNonNull: true), name: ClassPropertyName(name: r'voyage'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'VoyagesData$_Query$_voyages'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'VoyagesData$_Query$_voyages$_voyages', isNonNull: true), isNonNull: true), name: ClassPropertyName(name: r'voyages'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'VoyagesData$_Query'), properties: [ ClassProperty( type: TypeName( name: r'VoyagesData$_Query$_voyages', isNonNull: true), name: ClassPropertyName(name: r'voyages'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'DstMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'name'), isResolveType: false) ]), FragmentClassDefinition( name: FragmentName(name: r'DepartureMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false) ]), ClassDefinition( name: ClassName(name: r'PaginationInput'), properties: [ ClassProperty( type: DartTypeName(name: r'int', isNonNull: true), name: ClassPropertyName(name: r'limit'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int', isNonNull: true), name: ClassPropertyName(name: r'offset'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'PaginationInput', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin DstMixin { late String id; late String name; } mixin DepartureMixin { late String id; } @JsonSerializable(explicitToJson: true) class VoyagesData$Query$Voyages$Voyages$Voyage$Arrival extends JsonSerializable with EquatableMixin, DstMixin { VoyagesData$Query$Voyages$Voyages$Voyage$Arrival(); factory VoyagesData$Query$Voyages$Voyages$Voyage$Arrival.fromJson( Map json) => _$VoyagesData$Query$Voyages$Voyages$Voyage$ArrivalFromJson(json); @override List get props => [id, name]; @override Map toJson() => _$VoyagesData$Query$Voyages$Voyages$Voyage$ArrivalToJson(this); } @JsonSerializable(explicitToJson: true) class VoyagesData$Query$Voyages$Voyages$Voyage$Departure extends JsonSerializable with EquatableMixin, DepartureMixin { VoyagesData$Query$Voyages$Voyages$Voyage$Departure(); factory VoyagesData$Query$Voyages$Voyages$Voyage$Departure.fromJson( Map json) => _$VoyagesData$Query$Voyages$Voyages$Voyage$DepartureFromJson(json); @override List get props => [id]; @override Map toJson() => _$VoyagesData$Query$Voyages$Voyages$Voyage$DepartureToJson(this); } @JsonSerializable(explicitToJson: true) class VoyagesData$Query$Voyages$Voyages$Voyage extends JsonSerializable with EquatableMixin { VoyagesData$Query$Voyages$Voyages$Voyage(); factory VoyagesData$Query$Voyages$Voyages$Voyage.fromJson( Map json) => _$VoyagesData$Query$Voyages$Voyages$VoyageFromJson(json); late DateTime dateFrom; DateTime? dateTo; String? id; late String voyageNumber; late VoyagesData$Query$Voyages$Voyages$Voyage$Arrival arrival; late VoyagesData$Query$Voyages$Voyages$Voyage$Departure departure; @override List get props => [dateFrom, dateTo, id, voyageNumber, arrival, departure]; @override Map toJson() => _$VoyagesData$Query$Voyages$Voyages$VoyageToJson(this); } @JsonSerializable(explicitToJson: true) class VoyagesData$Query$Voyages$Voyages extends JsonSerializable with EquatableMixin { VoyagesData$Query$Voyages$Voyages(); factory VoyagesData$Query$Voyages$Voyages.fromJson( Map json) => _$VoyagesData$Query$Voyages$VoyagesFromJson(json); late int numberOfReports; late VoyagesData$Query$Voyages$Voyages$Voyage voyage; @override List get props => [numberOfReports, voyage]; @override Map toJson() => _$VoyagesData$Query$Voyages$VoyagesToJson(this); } @JsonSerializable(explicitToJson: true) class VoyagesData$Query$Voyages extends JsonSerializable with EquatableMixin { VoyagesData$Query$Voyages(); factory VoyagesData$Query$Voyages.fromJson(Map json) => _$VoyagesData$Query$VoyagesFromJson(json); late List voyages; @override List get props => [voyages]; @override Map toJson() => _$VoyagesData$Query$VoyagesToJson(this); } @JsonSerializable(explicitToJson: true) class VoyagesData$Query extends JsonSerializable with EquatableMixin { VoyagesData$Query(); factory VoyagesData$Query.fromJson(Map json) => _$VoyagesData$QueryFromJson(json); late VoyagesData$Query$Voyages voyages; @override List get props => [voyages]; @override Map toJson() => _$VoyagesData$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class PaginationInput extends JsonSerializable with EquatableMixin { PaginationInput({ required this.limit, required this.offset, }); factory PaginationInput.fromJson(Map json) => _$PaginationInputFromJson(json); late int limit; late int offset; @override List get props => [limit, offset]; @override Map toJson() => _$PaginationInputToJson(this); } @JsonSerializable(explicitToJson: true) class VoyagesDataArguments extends JsonSerializable with EquatableMixin { VoyagesDataArguments({required this.input}); @override factory VoyagesDataArguments.fromJson(Map json) => _$VoyagesDataArgumentsFromJson(json); late PaginationInput input; @override List get props => [input]; @override Map toJson() => _$VoyagesDataArgumentsToJson(this); } final VOYAGES_DATA_QUERY_DOCUMENT_OPERATION_NAME = 'VoyagesData'; final VOYAGES_DATA_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'VoyagesData'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'PaginationInput'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'voyages'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'pagination'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'voyages'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'numberOfReports'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'voyage'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'dateFrom'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'dateTo'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'voyageNumber'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'arrival'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Dst'), directives: [], ) ]), ), FieldNode( name: NameNode(value: 'departure'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FragmentSpreadNode( name: NameNode(value: 'Departure'), directives: [], ) ]), ), ]), ), ]), ) ]), ) ]), ), FragmentDefinitionNode( name: NameNode(value: 'Dst'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Destination'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'name'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ), FragmentDefinitionNode( name: NameNode(value: 'Departure'), typeCondition: TypeConditionNode( on: NamedTypeNode( name: NameNode(value: 'Destination'), isNonNull: false, )), directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'id'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ), ]); class VoyagesDataQuery extends GraphQLQuery { VoyagesDataQuery({required this.variables}); @override final DocumentNode document = VOYAGES_DATA_QUERY_DOCUMENT; @override final String operationName = VOYAGES_DATA_QUERY_DOCUMENT_OPERATION_NAME; @override final VoyagesDataArguments variables; @override List get props => [document, operationName, variables]; @override VoyagesData$Query parse(Map json) => VoyagesData$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/fragments/fragments_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On fragments', () { test( 'Fragments will have their own classes', () async => testGenerator( query: r''' fragment myFragment on SomeObject { s, i } query some_query { ...myFragment } ''', schema: r''' schema { query: SomeObject } type SomeObject { s: String i: Int } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_SomeObject'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject'), mixins: [FragmentName(name: r'MyFragmentMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'MyFragmentMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false) ]) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin MyFragmentMixin { String? s; int? i; } @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject extends JsonSerializable with EquatableMixin, MyFragmentMixin { SomeQuery$SomeObject(); factory SomeQuery$SomeObject.fromJson(Map json) => _$SomeQuery$SomeObjectFromJson(json); @override List get props => [s, i]; @override Map toJson() => _$SomeQuery$SomeObjectToJson(this); } '''; ================================================ FILE: test/query_generator/fragments/multiple_references_on_simple_naming_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; // While we don't have canonical classes generation, we can leverage class // deduplication on fragments expansion and use the generated fragment mixin // as "canonical data" when the fragment is the only selection of the field. // Example: // someObject { // ...myFragment // } void main() { test( 'On multiple reference of same fragment on simple naming', () async => testGenerator( query: r''' fragment myFragment on SomeObject { s, i } query some_query { someObject { ...myFragment } moreData { someObject { ...myFragment } } } ''', schema: r''' schema { query: QueryResponse } type QueryResponse { someObject: SomeObject moreData: MoreData } type MoreData { someObject: SomeObject } type SomeObject { s: String i: Int } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, namingScheme: 'simple', ), ); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_QueryResponse'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeObject'), mixins: [FragmentName(name: r'MyFragmentMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeObject'), mixins: [FragmentName(name: r'MyFragmentMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'MoreData'), properties: [ ClassProperty( type: TypeName(name: r'SomeObject'), name: ClassPropertyName(name: r'someObject'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryResponse'), properties: [ ClassProperty( type: TypeName(name: r'SomeObject'), name: ClassPropertyName(name: r'someObject'), isResolveType: false), ClassProperty( type: TypeName(name: r'MoreData'), name: ClassPropertyName(name: r'moreData'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'MyFragmentMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false) ]) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin MyFragmentMixin { String? s; int? i; } @JsonSerializable(explicitToJson: true) class SomeObject extends JsonSerializable with EquatableMixin, MyFragmentMixin { SomeObject(); factory SomeObject.fromJson(Map json) => _$SomeObjectFromJson(json); @override List get props => [s, i]; @override Map toJson() => _$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class MoreData extends JsonSerializable with EquatableMixin { MoreData(); factory MoreData.fromJson(Map json) => _$MoreDataFromJson(json); SomeObject? someObject; @override List get props => [someObject]; @override Map toJson() => _$MoreDataToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryResponse extends JsonSerializable with EquatableMixin { SomeQuery$QueryResponse(); factory SomeQuery$QueryResponse.fromJson(Map json) => _$SomeQuery$QueryResponseFromJson(json); SomeObject? someObject; MoreData? moreData; @override List get props => [someObject, moreData]; @override Map toJson() => _$SomeQuery$QueryResponseToJson(this); } '''; ================================================ FILE: test/query_generator/interfaces/interface_fragment_glob_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('Interface with fragment globs', () { test( 'Should handle correctly when interfaces use external fragments', () async => testGenerator( query: query, namingScheme: 'pathedWithFields', schema: graphQLSchema, libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: {'fragments_glob': '**.frag'}, sourceAssetsMap: { 'a|fragment.frag': fragmentsString, }, ), ); }); } const fragmentsString = ''' fragment UserFrag on User { id username } '''; const query = r''' query custom($id: ID!) { nodeById(id: $id) { id __typename, ... on User { ...UserFrag } ... on ChatMessage { message user { ...UserFrag } } } } '''; // https://graphql-code-generator.com/#live-demo final String graphQLSchema = r''' scalar String scalar ID schema { query: Query } type Query { nodeById(id: ID!): Node anoterNodeById(id: ID!): Node } interface Node { id: ID! } type User implements Node { id: ID! username: String! } type ChatMessage implements Node { id: ID! message: String! user: User! } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Query'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_Query$_nodeById$_user'), extension: ClassName(name: r'Custom$_Query$_nodeById'), mixins: [FragmentName(name: r'UserFragMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_nodeById$_chatMessage$_user'), mixins: [FragmentName(name: r'UserFragMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_nodeById$_chatMessage'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'message'), isResolveType: false), ClassProperty( type: TypeName( name: r'Custom$_Query$_nodeById$_chatMessage$_user', isNonNull: true), name: ClassPropertyName(name: r'user'), isResolveType: false) ], extension: ClassName(name: r'Custom$_Query$_nodeById'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_nodeById'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: { r'User': ClassName(name: r'Custom$_Query$_nodeById$_User'), r'ChatMessage': ClassName(name: r'Custom$_Query$_nodeById$_ChatMessage') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_Query$_nodeById'), name: ClassPropertyName(name: r'nodeById'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'UserFragMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'username'), isResolveType: false) ]) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'id')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin UserFragMixin { late String id; late String username; } @JsonSerializable(explicitToJson: true) class Custom$Query$NodeById$User extends Custom$Query$NodeById with EquatableMixin, UserFragMixin { Custom$Query$NodeById$User(); factory Custom$Query$NodeById$User.fromJson(Map json) => _$Custom$Query$NodeById$UserFromJson(json); @override List get props => [id, username]; @override Map toJson() => _$Custom$Query$NodeById$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$NodeById$ChatMessage$User extends JsonSerializable with EquatableMixin, UserFragMixin { Custom$Query$NodeById$ChatMessage$User(); factory Custom$Query$NodeById$ChatMessage$User.fromJson( Map json) => _$Custom$Query$NodeById$ChatMessage$UserFromJson(json); @override List get props => [id, username]; @override Map toJson() => _$Custom$Query$NodeById$ChatMessage$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$NodeById$ChatMessage extends Custom$Query$NodeById with EquatableMixin { Custom$Query$NodeById$ChatMessage(); factory Custom$Query$NodeById$ChatMessage.fromJson( Map json) => _$Custom$Query$NodeById$ChatMessageFromJson(json); late String message; late Custom$Query$NodeById$ChatMessage$User user; @override List get props => [message, user]; @override Map toJson() => _$Custom$Query$NodeById$ChatMessageToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$NodeById extends JsonSerializable with EquatableMixin { Custom$Query$NodeById(); factory Custom$Query$NodeById.fromJson(Map json) { switch (json['__typename'].toString()) { case r'User': return Custom$Query$NodeById$User.fromJson(json); case r'ChatMessage': return Custom$Query$NodeById$ChatMessage.fromJson(json); default: } return _$Custom$Query$NodeByIdFromJson(json); } late String id; @JsonKey(name: '__typename') String? $$typename; @override List get props => [id, $$typename]; @override Map toJson() { switch ($$typename) { case r'User': return (this as Custom$Query$NodeById$User).toJson(); case r'ChatMessage': return (this as Custom$Query$NodeById$ChatMessage).toJson(); default: } return _$Custom$Query$NodeByIdToJson(this); } } @JsonSerializable(explicitToJson: true) class Custom$Query extends JsonSerializable with EquatableMixin { Custom$Query(); factory Custom$Query.fromJson(Map json) => _$Custom$QueryFromJson(json); Custom$Query$NodeById? nodeById; @override List get props => [nodeById]; @override Map toJson() => _$Custom$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/interfaces/interface_possible_types_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On types not used by interfaces', () { test( 'Those other types are not considered nor generated', () async => testGenerator( query: query, schema: graphQLSchema, libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query custom($id: ID!) { nodeById(id: $id) { id __typename ... on User { username } ... on ChatMessage { message } } } '''; // https://graphql-code-generator.com/#live-demo const graphQLSchema = ''' scalar String scalar ID schema { query: Query } type Query { nodeById(id: ID!): Node } interface Node { id: ID! } type User implements Node { id: ID! username: String! } type OtherEntity implements Node { id: ID! test: String! } type ChatMessage implements Node { id: ID! message: String! user: User! } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Query'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_User'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'username'), isResolveType: false) ], extension: ClassName(name: r'Custom$_Query$_Node'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_ChatMessage'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'message'), isResolveType: false) ], extension: ClassName(name: r'Custom$_Query$_Node'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: { r'User': ClassName(name: r'Custom$_Query$_Node$_User'), r'ChatMessage': ClassName(name: r'Custom$_Query$_Node$_ChatMessage') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_Query$_Node'), name: ClassPropertyName(name: r'nodeById'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'id')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$Query$Node$User extends Custom$Query$Node with EquatableMixin { Custom$Query$Node$User(); factory Custom$Query$Node$User.fromJson(Map json) => _$Custom$Query$Node$UserFromJson(json); late String username; @override List get props => [username]; @override Map toJson() => _$Custom$Query$Node$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$ChatMessage extends Custom$Query$Node with EquatableMixin { Custom$Query$Node$ChatMessage(); factory Custom$Query$Node$ChatMessage.fromJson(Map json) => _$Custom$Query$Node$ChatMessageFromJson(json); late String message; @override List get props => [message]; @override Map toJson() => _$Custom$Query$Node$ChatMessageToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node extends JsonSerializable with EquatableMixin { Custom$Query$Node(); factory Custom$Query$Node.fromJson(Map json) { switch (json['__typename'].toString()) { case r'User': return Custom$Query$Node$User.fromJson(json); case r'ChatMessage': return Custom$Query$Node$ChatMessage.fromJson(json); default: } return _$Custom$Query$NodeFromJson(json); } late String id; @JsonKey(name: '__typename') String? $$typename; @override List get props => [id, $$typename]; @override Map toJson() { switch ($$typename) { case r'User': return (this as Custom$Query$Node$User).toJson(); case r'ChatMessage': return (this as Custom$Query$Node$ChatMessage).toJson(); default: } return _$Custom$Query$NodeToJson(this); } } @JsonSerializable(explicitToJson: true) class Custom$Query extends JsonSerializable with EquatableMixin { Custom$Query(); factory Custom$Query.fromJson(Map json) => _$Custom$QueryFromJson(json); Custom$Query$Node? nodeById; @override List get props => [nodeById]; @override Map toJson() => _$Custom$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/interfaces/interface_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On interfaces', () { test( 'On interfaces', () async => testGenerator( query: query, schema: graphQLSchema, libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' query custom($id: ID!) { nodeById(id: $id) { id __typename ... on User { ...UserFrag } ... on ChatMessage { message user { ...UserFrag } } } } fragment UserFrag on User { id username } '''; // https://graphql-code-generator.com/#live-demo final String graphQLSchema = r''' scalar String scalar ID schema { query: Query } type Query { nodeById(id: ID!): Node } interface Node { id: ID! } type User implements Node { id: ID! username: String! } type ChatMessage implements Node { id: ID! message: String! user: User! } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Query'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_User'), extension: ClassName(name: r'Custom$_Query$_Node'), mixins: [FragmentName(name: r'UserFragMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_ChatMessage$_User'), mixins: [FragmentName(name: r'UserFragMixin')], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node$_ChatMessage'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'message'), isResolveType: false), ClassProperty( type: TypeName( name: r'Custom$_Query$_Node$_ChatMessage$_User', isNonNull: true), name: ClassPropertyName(name: r'user'), isResolveType: false) ], extension: ClassName(name: r'Custom$_Query$_Node'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query$_Node'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: { r'User': ClassName(name: r'Custom$_Query$_Node$_User'), r'ChatMessage': ClassName(name: r'Custom$_Query$_Node$_ChatMessage') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Query'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_Query$_Node'), name: ClassPropertyName(name: r'nodeById'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), FragmentClassDefinition( name: FragmentName(name: r'UserFragMixin'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'username'), isResolveType: false) ]) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'id')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; mixin UserFragMixin { late String id; late String username; } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$User extends Custom$Query$Node with EquatableMixin, UserFragMixin { Custom$Query$Node$User(); factory Custom$Query$Node$User.fromJson(Map json) => _$Custom$Query$Node$UserFromJson(json); @override List get props => [id, username]; @override Map toJson() => _$Custom$Query$Node$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$ChatMessage$User extends JsonSerializable with EquatableMixin, UserFragMixin { Custom$Query$Node$ChatMessage$User(); factory Custom$Query$Node$ChatMessage$User.fromJson( Map json) => _$Custom$Query$Node$ChatMessage$UserFromJson(json); @override List get props => [id, username]; @override Map toJson() => _$Custom$Query$Node$ChatMessage$UserToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node$ChatMessage extends Custom$Query$Node with EquatableMixin { Custom$Query$Node$ChatMessage(); factory Custom$Query$Node$ChatMessage.fromJson(Map json) => _$Custom$Query$Node$ChatMessageFromJson(json); late String message; late Custom$Query$Node$ChatMessage$User user; @override List get props => [message, user]; @override Map toJson() => _$Custom$Query$Node$ChatMessageToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Query$Node extends JsonSerializable with EquatableMixin { Custom$Query$Node(); factory Custom$Query$Node.fromJson(Map json) { switch (json['__typename'].toString()) { case r'User': return Custom$Query$Node$User.fromJson(json); case r'ChatMessage': return Custom$Query$Node$ChatMessage.fromJson(json); default: } return _$Custom$Query$NodeFromJson(json); } late String id; @JsonKey(name: '__typename') String? $$typename; @override List get props => [id, $$typename]; @override Map toJson() { switch ($$typename) { case r'User': return (this as Custom$Query$Node$User).toJson(); case r'ChatMessage': return (this as Custom$Query$Node$ChatMessage).toJson(); default: } return _$Custom$Query$NodeToJson(this); } } @JsonSerializable(explicitToJson: true) class Custom$Query extends JsonSerializable with EquatableMixin { Custom$Query(); factory Custom$Query.fromJson(Map json) => _$Custom$QueryFromJson(json); Custom$Query$Node? nodeById; @override List get props => [nodeById]; @override Map toJson() => _$Custom$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/multiple_operations_per_file_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On generation', () { test( 'Allows multiple mutations per file', () async => testGenerator( query: query, schema: r''' schema { mutation: Mutation query: Query } type Mutation { mut(input: Input!): MutationResponse } type Query { que(intsNonNullable: [Int]!, stringNullable: String): QueryResponse } type QueryResponse { s: String i: Int list(intsNonNullable: [Int]!): [Int]! } type MutationResponse { s: String } input Input { s: String! } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } const query = r''' mutation MutData($input: Input!) { mut(input: $input) { s } } query QueData($intsNonNullable: [Int]!, $stringNullable: String) { que(intsNonNullable: $intsNonNullable, stringNullable: $stringNullable) { s i list(intsNonNullable: $intsNonNullable) } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'MutData$_Mutation'), operationName: r'MutData', classes: [ ClassDefinition( name: ClassName(name: r'MutData$_Mutation$_MutationResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'MutData$_Mutation'), properties: [ ClassProperty( type: TypeName(name: r'MutData$_Mutation$_MutationResponse'), name: ClassPropertyName(name: r'mut'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Mutation'), QueryDefinition( name: QueryName(name: r'QueData$_Query'), operationName: r'QueData', classes: [ ClassDefinition( name: ClassName(name: r'QueData$_Query$_QueryResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: true), name: ClassPropertyName(name: r'list'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'QueData$_Query'), properties: [ ClassProperty( type: TypeName(name: r'QueData$_Query$_QueryResponse'), name: ClassPropertyName(name: r'que'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], inputs: [ QueryInput( type: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: true), name: QueryInputName(name: r'intsNonNullable')), QueryInput( type: DartTypeName(name: r'String'), name: QueryInputName(name: r'stringNullable')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class MutData$Mutation$MutationResponse extends JsonSerializable with EquatableMixin { MutData$Mutation$MutationResponse(); factory MutData$Mutation$MutationResponse.fromJson( Map json) => _$MutData$Mutation$MutationResponseFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$MutData$Mutation$MutationResponseToJson(this); } @JsonSerializable(explicitToJson: true) class MutData$Mutation extends JsonSerializable with EquatableMixin { MutData$Mutation(); factory MutData$Mutation.fromJson(Map json) => _$MutData$MutationFromJson(json); MutData$Mutation$MutationResponse? mut; @override List get props => [mut]; @override Map toJson() => _$MutData$MutationToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({required this.s}); factory Input.fromJson(Map json) => _$InputFromJson(json); late String s; @override List get props => [s]; @override Map toJson() => _$InputToJson(this); } @JsonSerializable(explicitToJson: true) class QueData$Query$QueryResponse extends JsonSerializable with EquatableMixin { QueData$Query$QueryResponse(); factory QueData$Query$QueryResponse.fromJson(Map json) => _$QueData$Query$QueryResponseFromJson(json); String? s; int? i; late List list; @override List get props => [s, i, list]; @override Map toJson() => _$QueData$Query$QueryResponseToJson(this); } @JsonSerializable(explicitToJson: true) class QueData$Query extends JsonSerializable with EquatableMixin { QueData$Query(); factory QueData$Query.fromJson(Map json) => _$QueData$QueryFromJson(json); QueData$Query$QueryResponse? que; @override List get props => [que]; @override Map toJson() => _$QueData$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class MutDataArguments extends JsonSerializable with EquatableMixin { MutDataArguments({required this.input}); @override factory MutDataArguments.fromJson(Map json) => _$MutDataArgumentsFromJson(json); late Input input; @override List get props => [input]; @override Map toJson() => _$MutDataArgumentsToJson(this); } final MUT_DATA_MUTATION_DOCUMENT_OPERATION_NAME = 'MutData'; final MUT_DATA_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: 'MutData'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mut'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class MutDataMutation extends GraphQLQuery { MutDataMutation({required this.variables}); @override final DocumentNode document = MUT_DATA_MUTATION_DOCUMENT; @override final String operationName = MUT_DATA_MUTATION_DOCUMENT_OPERATION_NAME; @override final MutDataArguments variables; @override List get props => [document, operationName, variables]; @override MutData$Mutation parse(Map json) => MutData$Mutation.fromJson(json); } @JsonSerializable(explicitToJson: true) class QueDataArguments extends JsonSerializable with EquatableMixin { QueDataArguments({ required this.intsNonNullable, this.stringNullable, }); @override factory QueDataArguments.fromJson(Map json) => _$QueDataArgumentsFromJson(json); late List intsNonNullable; final String? stringNullable; @override List get props => [intsNonNullable, stringNullable]; @override Map toJson() => _$QueDataArgumentsToJson(this); } final QUE_DATA_QUERY_DOCUMENT_OPERATION_NAME = 'QueData'; final QUE_DATA_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'QueData'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'intsNonNullable')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: false, ), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'stringNullable')), type: NamedTypeNode( name: NameNode(value: 'String'), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'que'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'intsNonNullable'), value: VariableNode(name: NameNode(value: 'intsNonNullable')), ), ArgumentNode( name: NameNode(value: 'stringNullable'), value: VariableNode(name: NameNode(value: 'stringNullable')), ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'i'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'list'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'intsNonNullable'), value: VariableNode(name: NameNode(value: 'intsNonNullable')), ) ], directives: [], selectionSet: null, ), ]), ) ]), ) ]); class QueDataQuery extends GraphQLQuery { QueDataQuery({required this.variables}); @override final DocumentNode document = QUE_DATA_QUERY_DOCUMENT; @override final String operationName = QUE_DATA_QUERY_DOCUMENT_OPERATION_NAME; @override final QueDataArguments variables; @override List get props => [document, operationName, variables]; @override QueData$Query parse(Map json) => QueData$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/multiple_queries_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On multiple queries', () { test( 'Header and part should only be included once', () async => testGenerator( query: r'query some_query { s, i }', schema: r''' schema { query: SomeObject } type SomeObject { s: String i: Int } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, sourceAssetsMap: { 'a|queries/another_query.graphql': 'query another_query { s }', }, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_SomeObject'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query'), QueryDefinition( name: QueryName(name: r'AnotherQuery$_SomeObject'), operationName: r'another_query', classes: [ ClassDefinition( name: ClassName(name: r'AnotherQuery$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$SomeObject(); factory SomeQuery$SomeObject.fromJson(Map json) => _$SomeQuery$SomeObjectFromJson(json); String? s; int? i; @override List get props => [s, i]; @override Map toJson() => _$SomeQuery$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class AnotherQuery$SomeObject extends JsonSerializable with EquatableMixin { AnotherQuery$SomeObject(); factory AnotherQuery$SomeObject.fromJson(Map json) => _$AnotherQuery$SomeObjectFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$AnotherQuery$SomeObjectToJson(this); } '''; ================================================ FILE: test/query_generator/mutations_and_inputs/complex_input_objects_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On complex input objects', () { test( 'On complex input objects', () async => testGenerator( query: r''' query some_query($filter: ComplexInput!) { o(filter: $filter) { s } }''', schema: r''' schema { query: QueryRoot } type QueryRoot { o(filter: ComplexInput!): SomeObject } input ComplexInput { s: String! e: MyEnum ls: [String] i: [[Int]] } type SomeObject { s: String } enum MyEnum { value1 value2 } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_QueryRoot'), operationName: r'some_query', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'value1')), EnumValueDefinition(name: EnumValueName(name: r'value2')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryRoot$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'SomeQuery$_QueryRoot$_SomeObject'), name: ClassPropertyName(name: r'o'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'ComplexInput'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'String'), isNonNull: false), name: ClassPropertyName(name: r'ls'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: false), isNonNull: false), name: ClassPropertyName(name: r'i'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'ComplexInput', isNonNull: true), name: QueryInputName(name: r'filter')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$QueryRoot$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$QueryRoot$SomeObject(); factory SomeQuery$QueryRoot$SomeObject.fromJson(Map json) => _$SomeQuery$QueryRoot$SomeObjectFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$SomeQuery$QueryRoot$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryRoot extends JsonSerializable with EquatableMixin { SomeQuery$QueryRoot(); factory SomeQuery$QueryRoot.fromJson(Map json) => _$SomeQuery$QueryRootFromJson(json); SomeQuery$QueryRoot$SomeObject? o; @override List get props => [o]; @override Map toJson() => _$SomeQuery$QueryRootToJson(this); } @JsonSerializable(explicitToJson: true) class ComplexInput extends JsonSerializable with EquatableMixin { ComplexInput({ required this.s, this.e, this.ls, this.i, }); factory ComplexInput.fromJson(Map json) => _$ComplexInputFromJson(json); late String s; @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; List? ls; List?>? i; @override List get props => [s, e, ls, i]; @override Map toJson() => _$ComplexInputToJson(this); } enum MyEnum { @JsonValue('value1') value1, @JsonValue('value2') value2, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } @JsonSerializable(explicitToJson: true) class SomeQueryArguments extends JsonSerializable with EquatableMixin { SomeQueryArguments({required this.filter}); @override factory SomeQueryArguments.fromJson(Map json) => _$SomeQueryArgumentsFromJson(json); late ComplexInput filter; @override List get props => [filter]; @override Map toJson() => _$SomeQueryArgumentsToJson(this); } final SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'some_query'; final SOME_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'some_query'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'filter')), type: NamedTypeNode( name: NameNode(value: 'ComplexInput'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'o'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'filter'), value: VariableNode(name: NameNode(value: 'filter')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class SomeQueryQuery extends GraphQLQuery { SomeQueryQuery({required this.variables}); @override final DocumentNode document = SOME_QUERY_QUERY_DOCUMENT; @override final String operationName = SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override final SomeQueryArguments variables; @override List get props => [document, operationName, variables]; @override SomeQuery$QueryRoot parse(Map json) => SomeQuery$QueryRoot.fromJson(json); } '''; ================================================ FILE: test/query_generator/mutations_and_inputs/custom_scalars_on_input_objects_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On input objects', () { test( 'Custom scalars should be coerced', () async => testGenerator( query: query, schema: r''' scalar MyUuid schema { mutation: MutationRoot } type MutationRoot { mut(input: Input!, previousId: MyUuid, listIds: [MyUuid]): MutationResponse } type MutationResponse { s: String } input Input { id: MyUuid! idNullabe: MyUuid } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, builderOptionsMap: { 'scalar_mapping': [ { 'graphql_type': 'MyUuid', 'custom_parser_import': 'package:example/src/custom_parser.dart', 'dart_type': { 'name': 'MyUuid', 'imports': ['package:uuid/uuid.dart'], } }, ], }, ), ); }); } const query = r''' mutation custom($input: Input!, $previousId: MyUuid, $listIds: [MyUuid]) { mut(input: $input, previousId: $previousId, listIds: $listIds) { s } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_MutationRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_MutationRoot$_MutationResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_MutationRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_MutationRoot$_MutationResponse'), name: ClassPropertyName(name: r'mut'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: DartTypeName(name: r'MyUuid', isNonNull: true), name: ClassPropertyName(name: r'id'), annotations: [ r'JsonKey(fromJson: fromGraphQLMyUuidToDartMyUuid, toJson: fromDartMyUuidToGraphQLMyUuid)' ], isResolveType: false), ClassProperty( type: DartTypeName(name: r'MyUuid'), name: ClassPropertyName(name: r'idNullabe'), annotations: [ r'JsonKey(fromJson: fromGraphQLMyUuidNullableToDartMyUuidNullable, toJson: fromDartMyUuidNullableToGraphQLMyUuidNullable)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')), QueryInput( type: DartTypeName(name: r'MyUuid'), name: QueryInputName(name: r'previousId'), annotations: [ r'JsonKey(fromJson: fromGraphQLMyUuidNullableToDartMyUuidNullable, toJson: fromDartMyUuidNullableToGraphQLMyUuidNullable)' ]), QueryInput( type: ListOfTypeName( typeName: DartTypeName(name: r'MyUuid'), isNonNull: false), name: QueryInputName(name: r'listIds'), annotations: [ r'JsonKey(fromJson: fromGraphQLListNullableMyUuidNullableToDartListNullableMyUuidNullable, toJson: fromDartListNullableMyUuidNullableToGraphQLListNullableMyUuidNullable)' ]) ], generateHelpers: true, suffix: r'Mutation') ], customImports: [ r'package:uuid/uuid.dart', r'package:example/src/custom_parser.dart' ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; import 'package:uuid/uuid.dart'; import 'package:example/src/custom_parser.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$MutationRoot$MutationResponse extends JsonSerializable with EquatableMixin { Custom$MutationRoot$MutationResponse(); factory Custom$MutationRoot$MutationResponse.fromJson( Map json) => _$Custom$MutationRoot$MutationResponseFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$Custom$MutationRoot$MutationResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$MutationRoot extends JsonSerializable with EquatableMixin { Custom$MutationRoot(); factory Custom$MutationRoot.fromJson(Map json) => _$Custom$MutationRootFromJson(json); Custom$MutationRoot$MutationResponse? mut; @override List get props => [mut]; @override Map toJson() => _$Custom$MutationRootToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({ required this.id, this.idNullabe, }); factory Input.fromJson(Map json) => _$InputFromJson(json); @JsonKey( fromJson: fromGraphQLMyUuidToDartMyUuid, toJson: fromDartMyUuidToGraphQLMyUuid) late MyUuid id; @JsonKey( fromJson: fromGraphQLMyUuidNullableToDartMyUuidNullable, toJson: fromDartMyUuidNullableToGraphQLMyUuidNullable) MyUuid? idNullabe; @override List get props => [id, idNullabe]; @override Map toJson() => _$InputToJson(this); } @JsonSerializable(explicitToJson: true) class CustomArguments extends JsonSerializable with EquatableMixin { CustomArguments({ required this.input, this.previousId, this.listIds, }); @override factory CustomArguments.fromJson(Map json) => _$CustomArgumentsFromJson(json); late Input input; @JsonKey( fromJson: fromGraphQLMyUuidNullableToDartMyUuidNullable, toJson: fromDartMyUuidNullableToGraphQLMyUuidNullable) final MyUuid? previousId; @JsonKey( fromJson: fromGraphQLListNullableMyUuidNullableToDartListNullableMyUuidNullable, toJson: fromDartListNullableMyUuidNullableToGraphQLListNullableMyUuidNullable) final List? listIds; @override List get props => [input, previousId, listIds]; @override Map toJson() => _$CustomArgumentsToJson(this); } final CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME = 'custom'; final CUSTOM_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: 'custom'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'previousId')), type: NamedTypeNode( name: NameNode(value: 'MyUuid'), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'listIds')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'MyUuid'), isNonNull: false, ), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mut'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ), ArgumentNode( name: NameNode(value: 'previousId'), value: VariableNode(name: NameNode(value: 'previousId')), ), ArgumentNode( name: NameNode(value: 'listIds'), value: VariableNode(name: NameNode(value: 'listIds')), ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class CustomMutation extends GraphQLQuery { CustomMutation({required this.variables}); @override final DocumentNode document = CUSTOM_MUTATION_DOCUMENT; @override final String operationName = CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME; @override final CustomArguments variables; @override List get props => [document, operationName, variables]; @override Custom$MutationRoot parse(Map json) => Custom$MutationRoot.fromJson(json); } '''; ================================================ FILE: test/query_generator/mutations_and_inputs/filter_input_objects_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On complex input objects', () { test( 'Unused input objects will be filtered out', () async => testGenerator( query: r''' query some_query($input: Input!) { o(input: $input) { s } }''', schema: r''' schema { query: QueryRoot } type QueryRoot { o(input: Input!): SomeObject } input Input { s: SubInput } input SubInput { s: String } input UnusedInput { a: String u: UnusedSubInput } input UnusedSubInput { a: String } type SomeObject { s: String } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_QueryRoot'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryRoot$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'SomeQuery$_QueryRoot$_SomeObject'), name: ClassPropertyName(name: r'o'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: TypeName(name: r'SubInput'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true), ClassDefinition( name: ClassName(name: r'SubInput'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$QueryRoot$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$QueryRoot$SomeObject(); factory SomeQuery$QueryRoot$SomeObject.fromJson(Map json) => _$SomeQuery$QueryRoot$SomeObjectFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$SomeQuery$QueryRoot$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$QueryRoot extends JsonSerializable with EquatableMixin { SomeQuery$QueryRoot(); factory SomeQuery$QueryRoot.fromJson(Map json) => _$SomeQuery$QueryRootFromJson(json); SomeQuery$QueryRoot$SomeObject? o; @override List get props => [o]; @override Map toJson() => _$SomeQuery$QueryRootToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({this.s}); factory Input.fromJson(Map json) => _$InputFromJson(json); SubInput? s; @override List get props => [s]; @override Map toJson() => _$InputToJson(this); } @JsonSerializable(explicitToJson: true) class SubInput extends JsonSerializable with EquatableMixin { SubInput({this.s}); factory SubInput.fromJson(Map json) => _$SubInputFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$SubInputToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQueryArguments extends JsonSerializable with EquatableMixin { SomeQueryArguments({required this.input}); @override factory SomeQueryArguments.fromJson(Map json) => _$SomeQueryArgumentsFromJson(json); late Input input; @override List get props => [input]; @override Map toJson() => _$SomeQueryArgumentsToJson(this); } final SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'some_query'; final SOME_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'some_query'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'o'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class SomeQueryQuery extends GraphQLQuery { SomeQueryQuery({required this.variables}); @override final DocumentNode document = SOME_QUERY_QUERY_DOCUMENT; @override final String operationName = SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override final SomeQueryArguments variables; @override List get props => [document, operationName, variables]; @override SomeQuery$QueryRoot parse(Map json) => SomeQuery$QueryRoot.fromJson(json); } '''; ================================================ FILE: test/query_generator/mutations_and_inputs/input_duplication_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('Input duplication', () { test( 'The input objects should not duplicate', () async => testGenerator( query: query, namingScheme: 'pathedWithFields', schema: r''' schema { mutation: Mutation } type Mutation { mut(input: Input!): MutationResponse mutList(input: [Input!]!): MutationResponse } type MutationResponse { s: String } input Input { s: String! } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, sourceAssetsMap: { 'a|queries/another_query.graphql': anotherQuery, }, generateHelpers: true, ), ); }); } const query = r''' mutation custom($input: Input!) { mut(input: $input) { s } } '''; const anotherQuery = r''' mutation customList($input: [Input!]!) { mutList(input: $input) { s } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Mutation'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_Mutation$_mut'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_Mutation'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_Mutation$_mut'), name: ClassPropertyName(name: r'mut'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Mutation'), QueryDefinition( name: QueryName(name: r'CustomList$_Mutation'), operationName: r'customList', classes: [ ClassDefinition( name: ClassName(name: r'CustomList$_Mutation$_mutList'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CustomList$_Mutation'), properties: [ ClassProperty( type: TypeName(name: r'CustomList$_Mutation$_mutList'), name: ClassPropertyName(name: r'mutList'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: ListOfTypeName( typeName: TypeName(name: r'Input', isNonNull: true), isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Mutation') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$Mutation$Mut extends JsonSerializable with EquatableMixin { Custom$Mutation$Mut(); factory Custom$Mutation$Mut.fromJson(Map json) => _$Custom$Mutation$MutFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$Custom$Mutation$MutToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$Mutation extends JsonSerializable with EquatableMixin { Custom$Mutation(); factory Custom$Mutation.fromJson(Map json) => _$Custom$MutationFromJson(json); Custom$Mutation$Mut? mut; @override List get props => [mut]; @override Map toJson() => _$Custom$MutationToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({required this.s}); factory Input.fromJson(Map json) => _$InputFromJson(json); late String s; @override List get props => [s]; @override Map toJson() => _$InputToJson(this); } @JsonSerializable(explicitToJson: true) class CustomList$Mutation$MutList extends JsonSerializable with EquatableMixin { CustomList$Mutation$MutList(); factory CustomList$Mutation$MutList.fromJson(Map json) => _$CustomList$Mutation$MutListFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$CustomList$Mutation$MutListToJson(this); } @JsonSerializable(explicitToJson: true) class CustomList$Mutation extends JsonSerializable with EquatableMixin { CustomList$Mutation(); factory CustomList$Mutation.fromJson(Map json) => _$CustomList$MutationFromJson(json); CustomList$Mutation$MutList? mutList; @override List get props => [mutList]; @override Map toJson() => _$CustomList$MutationToJson(this); } @JsonSerializable(explicitToJson: true) class CustomArguments extends JsonSerializable with EquatableMixin { CustomArguments({required this.input}); @override factory CustomArguments.fromJson(Map json) => _$CustomArgumentsFromJson(json); late Input input; @override List get props => [input]; @override Map toJson() => _$CustomArgumentsToJson(this); } final CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME = 'custom'; final CUSTOM_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: 'custom'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mut'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class CustomMutation extends GraphQLQuery { CustomMutation({required this.variables}); @override final DocumentNode document = CUSTOM_MUTATION_DOCUMENT; @override final String operationName = CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME; @override final CustomArguments variables; @override List get props => [document, operationName, variables]; @override Custom$Mutation parse(Map json) => Custom$Mutation.fromJson(json); } @JsonSerializable(explicitToJson: true) class CustomListArguments extends JsonSerializable with EquatableMixin { CustomListArguments({required this.input}); @override factory CustomListArguments.fromJson(Map json) => _$CustomListArgumentsFromJson(json); late List input; @override List get props => [input]; @override Map toJson() => _$CustomListArgumentsToJson(this); } final CUSTOM_LIST_MUTATION_DOCUMENT_OPERATION_NAME = 'customList'; final CUSTOM_LIST_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: 'customList'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mutList'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class CustomListMutation extends GraphQLQuery { CustomListMutation({required this.variables}); @override final DocumentNode document = CUSTOM_LIST_MUTATION_DOCUMENT; @override final String operationName = CUSTOM_LIST_MUTATION_DOCUMENT_OPERATION_NAME; @override final CustomListArguments variables; @override List get props => [document, operationName, variables]; @override CustomList$Mutation parse(Map json) => CustomList$Mutation.fromJson(json); } '''; ================================================ FILE: test/query_generator/mutations_and_inputs/mutations_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On mutations', () { test( 'The mutation class will be suffixed as Mutation', () async => testGenerator( query: query, schema: r''' schema { mutation: MutationRoot } type MutationRoot { mut(input: Input!): MutationResponse _mut(input: _Input!): _MutationResponse } type MutationResponse { s: String } type _MutationResponse { _s: String } input Input { s: String! } input _Input { _s: String! } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, sourceAssetsMap: { 'a|queries/another_query.graphql': anotherQuery, }, ), ); }); } const query = r''' mutation custom($input: Input!) { mut(input: $input) { s } } '''; const anotherQuery = r''' mutation _custom($input: _Input!) { _mut(input: $input) { _s } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_MutationRoot'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_MutationRoot$_MutationResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Custom$_MutationRoot'), properties: [ ClassProperty( type: TypeName(name: r'Custom$_MutationRoot$_MutationResponse'), name: ClassPropertyName(name: r'mut'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Mutation'), QueryDefinition( name: QueryName(name: r'$custom$_MutationRoot'), operationName: r'_custom', classes: [ ClassDefinition( name: ClassName(name: r'$custom$_MutationRoot$_$MutationResponse'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_s'), annotations: [r'''JsonKey(name: '_s')'''], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'$custom$_MutationRoot'), properties: [ ClassProperty( type: TypeName( name: r'$custom$_MutationRoot$_$MutationResponse'), name: ClassPropertyName(name: r'_mut'), annotations: [r'''JsonKey(name: '_mut')'''], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'_Input'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'_s'), annotations: [r'''JsonKey(name: '_s')'''], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'_Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: true, suffix: r'Mutation') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$MutationRoot$MutationResponse extends JsonSerializable with EquatableMixin { Custom$MutationRoot$MutationResponse(); factory Custom$MutationRoot$MutationResponse.fromJson( Map json) => _$Custom$MutationRoot$MutationResponseFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$Custom$MutationRoot$MutationResponseToJson(this); } @JsonSerializable(explicitToJson: true) class Custom$MutationRoot extends JsonSerializable with EquatableMixin { Custom$MutationRoot(); factory Custom$MutationRoot.fromJson(Map json) => _$Custom$MutationRootFromJson(json); Custom$MutationRoot$MutationResponse? mut; @override List get props => [mut]; @override Map toJson() => _$Custom$MutationRootToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({required this.s}); factory Input.fromJson(Map json) => _$InputFromJson(json); late String s; @override List get props => [s]; @override Map toJson() => _$InputToJson(this); } @JsonSerializable(explicitToJson: true) class $custom$MutationRoot$$MutationResponse extends JsonSerializable with EquatableMixin { $custom$MutationRoot$$MutationResponse(); factory $custom$MutationRoot$$MutationResponse.fromJson( Map json) => _$$custom$MutationRoot$$MutationResponseFromJson(json); @JsonKey(name: '_s') String? $s; @override List get props => [$s]; @override Map toJson() => _$$custom$MutationRoot$$MutationResponseToJson(this); } @JsonSerializable(explicitToJson: true) class $custom$MutationRoot extends JsonSerializable with EquatableMixin { $custom$MutationRoot(); factory $custom$MutationRoot.fromJson(Map json) => _$$custom$MutationRootFromJson(json); @JsonKey(name: '_mut') $custom$MutationRoot$$MutationResponse? $mut; @override List get props => [$mut]; @override Map toJson() => _$$custom$MutationRootToJson(this); } @JsonSerializable(explicitToJson: true) class $Input extends JsonSerializable with EquatableMixin { $Input({required this.$s}); factory $Input.fromJson(Map json) => _$$InputFromJson(json); @JsonKey(name: '_s') late String $s; @override List get props => [$s]; @override Map toJson() => _$$InputToJson(this); } @JsonSerializable(explicitToJson: true) class CustomArguments extends JsonSerializable with EquatableMixin { CustomArguments({required this.input}); @override factory CustomArguments.fromJson(Map json) => _$CustomArgumentsFromJson(json); late Input input; @override List get props => [input]; @override Map toJson() => _$CustomArgumentsToJson(this); } final CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME = 'custom'; final CUSTOM_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: 'custom'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'mut'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class CustomMutation extends GraphQLQuery { CustomMutation({required this.variables}); @override final DocumentNode document = CUSTOM_MUTATION_DOCUMENT; @override final String operationName = CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME; @override final CustomArguments variables; @override List get props => [document, operationName, variables]; @override Custom$MutationRoot parse(Map json) => Custom$MutationRoot.fromJson(json); } @JsonSerializable(explicitToJson: true) class $customArguments extends JsonSerializable with EquatableMixin { $customArguments({required this.input}); @override factory $customArguments.fromJson(Map json) => _$$customArgumentsFromJson(json); late $Input input; @override List get props => [input]; @override Map toJson() => _$$customArgumentsToJson(this); } final $CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME = '_custom'; final $CUSTOM_MUTATION_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.mutation, name: NameNode(value: '_custom'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'input')), type: NamedTypeNode( name: NameNode(value: '_Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: '_mut'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'input'), value: VariableNode(name: NameNode(value: 'input')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: '_s'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class $customMutation extends GraphQLQuery<$custom$MutationRoot, $customArguments> { $customMutation({required this.variables}); @override final DocumentNode document = $CUSTOM_MUTATION_DOCUMENT; @override final String operationName = $CUSTOM_MUTATION_DOCUMENT_OPERATION_NAME; @override final $customArguments variables; @override List get props => [document, operationName, variables]; @override $custom$MutationRoot parse(Map json) => $custom$MutationRoot.fromJson(json); } '''; ================================================ FILE: test/query_generator/mutations_and_inputs/non_nullable_list_inputs_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On query generation', () { test( 'Non-nullability on inputs (considering lists)', () async => testGenerator( query: r''' query some_query($i: Int, $inn: Int!, $li: [Int], $linn: [Int!], $lnni: [Int]!, $lnninn: [Int!]!, $matrix: [[Int]], $matrixnn: [[Int!]!]!) { someQuery(i: $i, inn: $inn, li: $li, linn: $linn, lnni: $lnni, lnninn: $lnninn, matrix: $matrix, matrixnn: $matrixnn) { s } } ''', schema: r''' type Query { someQuery(i: Int, inn: Int!, li: [Int], linn: [Int!], lnni: [Int]!, lnninn: [Int!]!, matrix: [[Int]], matrixnn: [[Int!]!]!): SomeObject } type SomeObject { s: String } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_Query'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_Query$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_Query'), properties: [ ClassProperty( type: TypeName( name: r'SomeQuery$_Query$_SomeObject'), name: ClassPropertyName(name: r'someQuery'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], inputs: [ QueryInput( type: DartTypeName(name: r'int'), name: QueryInputName(name: r'i')), QueryInput( type: DartTypeName(name: r'int', isNonNull: true), name: QueryInputName(name: r'inn')), QueryInput( type: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: false), name: QueryInputName(name: r'li')), QueryInput( type: ListOfTypeName( typeName: DartTypeName(name: r'int', isNonNull: true), isNonNull: false), name: QueryInputName(name: r'linn')), QueryInput( type: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: true), name: QueryInputName(name: r'lnni')), QueryInput( type: ListOfTypeName( typeName: DartTypeName(name: r'int', isNonNull: true), isNonNull: true), name: QueryInputName(name: r'lnninn')), QueryInput( type: ListOfTypeName( typeName: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: false), isNonNull: false), name: QueryInputName(name: r'matrix')), QueryInput( type: ListOfTypeName( typeName: ListOfTypeName( typeName: DartTypeName(name: r'int', isNonNull: true), isNonNull: true), isNonNull: true), name: QueryInputName(name: r'matrixnn')) ], generateHelpers: true, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$Query$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$Query$SomeObject(); factory SomeQuery$Query$SomeObject.fromJson(Map json) => _$SomeQuery$Query$SomeObjectFromJson(json); String? s; @override List get props => [s]; @override Map toJson() => _$SomeQuery$Query$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$Query extends JsonSerializable with EquatableMixin { SomeQuery$Query(); factory SomeQuery$Query.fromJson(Map json) => _$SomeQuery$QueryFromJson(json); SomeQuery$Query$SomeObject? someQuery; @override List get props => [someQuery]; @override Map toJson() => _$SomeQuery$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQueryArguments extends JsonSerializable with EquatableMixin { SomeQueryArguments({ this.i, required this.inn, this.li, this.linn, required this.lnni, required this.lnninn, this.matrix, required this.matrixnn, }); @override factory SomeQueryArguments.fromJson(Map json) => _$SomeQueryArgumentsFromJson(json); final int? i; late int inn; final List? li; final List? linn; late List lnni; late List lnninn; final List?>? matrix; late List> matrixnn; @override List get props => [i, inn, li, linn, lnni, lnninn, matrix, matrixnn]; @override Map toJson() => _$SomeQueryArgumentsToJson(this); } final SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'some_query'; final SOME_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'some_query'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'i')), type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'inn')), type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'li')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: false, ), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'linn')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: true, ), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'lnni')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: false, ), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'lnninn')), type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: true, ), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'matrix')), type: ListTypeNode( type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: false, ), isNonNull: false, ), isNonNull: false, ), defaultValue: DefaultValueNode(value: null), directives: [], ), VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'matrixnn')), type: ListTypeNode( type: ListTypeNode( type: NamedTypeNode( name: NameNode(value: 'Int'), isNonNull: true, ), isNonNull: true, ), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'someQuery'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'i'), value: VariableNode(name: NameNode(value: 'i')), ), ArgumentNode( name: NameNode(value: 'inn'), value: VariableNode(name: NameNode(value: 'inn')), ), ArgumentNode( name: NameNode(value: 'li'), value: VariableNode(name: NameNode(value: 'li')), ), ArgumentNode( name: NameNode(value: 'linn'), value: VariableNode(name: NameNode(value: 'linn')), ), ArgumentNode( name: NameNode(value: 'lnni'), value: VariableNode(name: NameNode(value: 'lnni')), ), ArgumentNode( name: NameNode(value: 'lnninn'), value: VariableNode(name: NameNode(value: 'lnninn')), ), ArgumentNode( name: NameNode(value: 'matrix'), value: VariableNode(name: NameNode(value: 'matrix')), ), ArgumentNode( name: NameNode(value: 'matrixnn'), value: VariableNode(name: NameNode(value: 'matrixnn')), ), ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 's'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]); class SomeQueryQuery extends GraphQLQuery { SomeQueryQuery({required this.variables}); @override final DocumentNode document = SOME_QUERY_QUERY_DOCUMENT; @override final String operationName = SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override final SomeQueryArguments variables; @override List get props => [document, operationName, variables]; @override SomeQuery$Query parse(Map json) => SomeQuery$Query.fromJson(json); } ''', generateHelpers: true)); }); } ================================================ FILE: test/query_generator/mutations_and_inputs/recursive_input_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('Recursive input objects', () { test( r'''Artemis won't StackOverflow on recursive input objects''', () async => testGenerator( query: query, schema: r''' type Mutation { mut(input: Input!): String } input Input { and: Input or: Input } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' mutation custom($input: Input!) { mut(input: $input) } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Custom$_Mutation'), operationName: r'custom', classes: [ ClassDefinition( name: ClassName(name: r'Custom$_Mutation'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'mut'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: TypeName(name: r'Input'), name: ClassPropertyName(name: r'and'), isResolveType: false), ClassProperty( type: TypeName(name: r'Input'), name: ClassPropertyName(name: r'or'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'input')) ], generateHelpers: false, suffix: r'Mutation') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Custom$Mutation extends JsonSerializable with EquatableMixin { Custom$Mutation(); factory Custom$Mutation.fromJson(Map json) => _$Custom$MutationFromJson(json); String? mut; @override List get props => [mut]; @override Map toJson() => _$Custom$MutationToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({ this.and, this.or, }); factory Input.fromJson(Map json) => _$InputFromJson(json); Input? and; Input? or; @override List get props => [and, or]; @override Map toJson() => _$InputToJson(this); } '''; ================================================ FILE: test/query_generator/naming/casing_conversion_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On types/fields names', () { test( 'Casing will be converted accordingly (and JsonKey names willb e populated accordingly)', () async => testGenerator( query: r''' query some_query($filter: Input!) { query(filter: $filter) { camelCaseField PascalCaseField snake_case_field SCREAMING_SNAKE_CASE_FIELD e } }''', schema: r''' type Query { query(input: Input): SomeObject } input Input { camelCaseField: camelCaseTypeInput PascalCaseField: PascalCaseTypeInput snake_case_field: snake_case_type_input SCREAMING_SNAKE_CASE_FIELD: SCREAMING_SNAKE_CASE_TYPE_INPUT e: MyEnum } type SomeObject { camelCaseField: camelCaseType PascalCaseField: PascalCaseType snake_case_field: snake_case_type SCREAMING_SNAKE_CASE_FIELD: SCREAMING_SNAKE_CASE_TYPE e: MyEnum } type camelCaseType { s: String } type PascalCaseType { s: String } type snake_case_type { s: String } type SCREAMING_SNAKE_CASE_TYPE { s: String } type camelCaseTypeInput { s: String } type PascalCaseTypeInput { s: String } type snake_case_type_input { s: String } type SCREAMING_SNAKE_CASE_TYPE_INPUT { s: String } enum MyEnum { camelCase PascalCase snake_case SCREAMING_SNAKE_CASE } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, namingScheme: 'simple', ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_Query'), operationName: r'some_query', classes: [ EnumDefinition(name: EnumName(name: r'MyEnum'), values: [ EnumValueDefinition(name: EnumValueName(name: r'camelCase')), EnumValueDefinition(name: EnumValueName(name: r'PascalCase')), EnumValueDefinition(name: EnumValueName(name: r'snake_case')), EnumValueDefinition( name: EnumValueName(name: r'SCREAMING_SNAKE_CASE')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'SomeObject'), properties: [ ClassProperty( type: TypeName(name: r'CamelCaseType'), name: ClassPropertyName(name: r'camelCaseField'), isResolveType: false), ClassProperty( type: TypeName(name: r'PascalCaseType'), name: ClassPropertyName(name: r'PascalCaseField'), annotations: [r'''JsonKey(name: 'PascalCaseField')'''], isResolveType: false), ClassProperty( type: TypeName(name: r'SnakeCaseType'), name: ClassPropertyName(name: r'snake_case_field'), annotations: [r'''JsonKey(name: 'snake_case_field')'''], isResolveType: false), ClassProperty( type: TypeName(name: r'ScreamingSnakeCaseType'), name: ClassPropertyName(name: r'SCREAMING_SNAKE_CASE_FIELD'), annotations: [ r'''JsonKey(name: 'SCREAMING_SNAKE_CASE_FIELD')''' ], isResolveType: false), ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_Query'), properties: [ ClassProperty( type: TypeName(name: r'SomeObject'), name: ClassPropertyName(name: r'query'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'Input'), properties: [ ClassProperty( type: TypeName(name: r'CamelCaseTypeInput'), name: ClassPropertyName(name: r'camelCaseField'), isResolveType: false), ClassProperty( type: TypeName(name: r'PascalCaseTypeInput'), name: ClassPropertyName(name: r'PascalCaseField'), annotations: [r'''JsonKey(name: 'PascalCaseField')'''], isResolveType: false), ClassProperty( type: TypeName(name: r'SnakeCaseTypeInput'), name: ClassPropertyName(name: r'snake_case_field'), annotations: [r'''JsonKey(name: 'snake_case_field')'''], isResolveType: false), ClassProperty( type: TypeName(name: r'ScreamingSnakeCaseTypeInput'), name: ClassPropertyName(name: r'SCREAMING_SNAKE_CASE_FIELD'), annotations: [ r'''JsonKey(name: 'SCREAMING_SNAKE_CASE_FIELD')''' ], isResolveType: false), ClassProperty( type: TypeName(name: r'MyEnum'), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(unknownEnumValue: MyEnum.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: true) ], inputs: [ QueryInput( type: TypeName(name: r'Input', isNonNull: true), name: QueryInputName(name: r'filter')) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeObject extends JsonSerializable with EquatableMixin { SomeObject(); factory SomeObject.fromJson(Map json) => _$SomeObjectFromJson(json); CamelCaseType? camelCaseField; @JsonKey(name: 'PascalCaseField') PascalCaseType? pascalCaseField; @JsonKey(name: 'snake_case_field') SnakeCaseType? snakeCaseField; @JsonKey(name: 'SCREAMING_SNAKE_CASE_FIELD') ScreamingSnakeCaseType? screamingSnakeCaseField; @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; @override List get props => [ camelCaseField, pascalCaseField, snakeCaseField, screamingSnakeCaseField, e ]; @override Map toJson() => _$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$Query extends JsonSerializable with EquatableMixin { SomeQuery$Query(); factory SomeQuery$Query.fromJson(Map json) => _$SomeQuery$QueryFromJson(json); SomeObject? query; @override List get props => [query]; @override Map toJson() => _$SomeQuery$QueryToJson(this); } @JsonSerializable(explicitToJson: true) class Input extends JsonSerializable with EquatableMixin { Input({ this.camelCaseField, this.pascalCaseField, this.snakeCaseField, this.screamingSnakeCaseField, this.e, }); factory Input.fromJson(Map json) => _$InputFromJson(json); CamelCaseTypeInput? camelCaseField; @JsonKey(name: 'PascalCaseField') PascalCaseTypeInput? pascalCaseField; @JsonKey(name: 'snake_case_field') SnakeCaseTypeInput? snakeCaseField; @JsonKey(name: 'SCREAMING_SNAKE_CASE_FIELD') ScreamingSnakeCaseTypeInput? screamingSnakeCaseField; @JsonKey(unknownEnumValue: MyEnum.artemisUnknown) MyEnum? e; @override List get props => [ camelCaseField, pascalCaseField, snakeCaseField, screamingSnakeCaseField, e ]; @override Map toJson() => _$InputToJson(this); } enum MyEnum { @JsonValue('camelCase') camelCase, @JsonValue('PascalCase') pascalCase, @JsonValue('snake_case') snakeCase, @JsonValue('SCREAMING_SNAKE_CASE') screamingSnakeCase, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } @JsonSerializable(explicitToJson: true) class SomeQueryArguments extends JsonSerializable with EquatableMixin { SomeQueryArguments({required this.filter}); @override factory SomeQueryArguments.fromJson(Map json) => _$SomeQueryArgumentsFromJson(json); late Input filter; @override List get props => [filter]; @override Map toJson() => _$SomeQueryArgumentsToJson(this); } final SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME = 'some_query'; final SOME_QUERY_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'some_query'), variableDefinitions: [ VariableDefinitionNode( variable: VariableNode(name: NameNode(value: 'filter')), type: NamedTypeNode( name: NameNode(value: 'Input'), isNonNull: true, ), defaultValue: DefaultValueNode(value: null), directives: [], ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'query'), alias: null, arguments: [ ArgumentNode( name: NameNode(value: 'filter'), value: VariableNode(name: NameNode(value: 'filter')), ) ], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'camelCaseField'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'PascalCaseField'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'snake_case_field'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'SCREAMING_SNAKE_CASE_FIELD'), alias: null, arguments: [], directives: [], selectionSet: null, ), FieldNode( name: NameNode(value: 'e'), alias: null, arguments: [], directives: [], selectionSet: null, ), ]), ) ]), ) ]); class SomeQueryQuery extends GraphQLQuery { SomeQueryQuery({required this.variables}); @override final DocumentNode document = SOME_QUERY_QUERY_DOCUMENT; @override final String operationName = SOME_QUERY_QUERY_DOCUMENT_OPERATION_NAME; @override final SomeQueryArguments variables; @override List get props => [document, operationName, variables]; @override SomeQuery$Query parse(Map json) => SomeQuery$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/naming/common.dart ================================================ const schema = r''' enum SomeEnum { e1 e2 } input Input { i: String e: SomeEnum s: SubInput } input SubInput { s: String } type Thing { id: String e: SomeEnum aThing: Thing bThing: Thing fThing: Thing } type Query { thing(input: Input!): Thing } '''; const query = r''' query big_query($input: Input!) { thing(input: $input) { e ... on Thing { id } ...parts aThing { id } bThing { id } aliasOnAThing: aThing { id } } aliasOnThing: thing(input: $input) { e ... on Thing { id } ...parts aThing { id } bThing { id } aliasOnAThing: aThing { id } } } fragment parts on Thing { id fThing { id } aliasOnFThing: fThing { id } } '''; ================================================ FILE: test/query_generator/naming/pathed_with_fields_test.dart ================================================ import 'package:test/test.dart'; import '../../helpers.dart'; import 'common.dart'; void main() { group('On naming', () { test( 'On pathedWithFields naming scheme', () async => testNaming( query: query, schema: schema, expectedNames: _expectedNames, namingScheme: 'pathedWithFields', ), ); }); } final _expectedNames = [ r'SomeEnum', r'BigQuery$Query$Thing$AThing', r'BigQuery$Query$Thing$BThing', r'BigQuery$Query$Thing$AliasOnAThing', r'BigQuery$Query$Thing', r'BigQuery$Query$AliasOnThing$AThing', r'BigQuery$Query$AliasOnThing$BThing', r'BigQuery$Query$AliasOnThing$AliasOnAThing', r'BigQuery$Query$AliasOnThing', r'BigQuery$Query', r'PartsMixin$FThing', r'PartsMixin$AliasOnFThing', r'PartsMixin', r'Input', r'SubInput' ]; ================================================ FILE: test/query_generator/naming/simple_naming_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('Simple naming', () { test( 'Casing will be converted accordingly (and JsonKey names will be populated accordingly)', () async => testGenerator( query: r''' query ClientEventsData { clientEvents { items { type } } } ''', schema: r''' type ClientEvent { type: Int! } type ClientEventItem { type: Int! } type ClientEventPage { items: [ClientEventItem!]! totalCount: Int! } type ClientPage { totalCount: Int! } type Query { clientEvents: ClientEventPage! clients: ClientPage! } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, generateHelpers: true, namingScheme: 'simple', ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'ClientEventsData$_Query'), operationName: r'ClientEventsData', classes: [ ClassDefinition( name: ClassName(name: r'ClientEventItem'), properties: [ ClassProperty( type: DartTypeName(name: r'int', isNonNull: true), name: ClassPropertyName(name: r'type'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'ClientEventPage'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName(name: r'ClientEventItem', isNonNull: true), isNonNull: true), name: ClassPropertyName(name: r'items'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'ClientEventsData$_Query'), properties: [ ClassProperty( type: TypeName(name: r'ClientEventPage', isNonNull: true), name: ClassPropertyName(name: r'clientEvents'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: true, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:artemis/artemis.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class ClientEventItem extends JsonSerializable with EquatableMixin { ClientEventItem(); factory ClientEventItem.fromJson(Map json) => _$ClientEventItemFromJson(json); late int type; @override List get props => [type]; @override Map toJson() => _$ClientEventItemToJson(this); } @JsonSerializable(explicitToJson: true) class ClientEventPage extends JsonSerializable with EquatableMixin { ClientEventPage(); factory ClientEventPage.fromJson(Map json) => _$ClientEventPageFromJson(json); late List items; @override List get props => [items]; @override Map toJson() => _$ClientEventPageToJson(this); } @JsonSerializable(explicitToJson: true) class ClientEventsData$Query extends JsonSerializable with EquatableMixin { ClientEventsData$Query(); factory ClientEventsData$Query.fromJson(Map json) => _$ClientEventsData$QueryFromJson(json); late ClientEventPage clientEvents; @override List get props => [clientEvents]; @override Map toJson() => _$ClientEventsData$QueryToJson(this); } final CLIENT_EVENTS_DATA_QUERY_DOCUMENT_OPERATION_NAME = 'ClientEventsData'; final CLIENT_EVENTS_DATA_QUERY_DOCUMENT = DocumentNode(definitions: [ OperationDefinitionNode( type: OperationType.query, name: NameNode(value: 'ClientEventsData'), variableDefinitions: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'clientEvents'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'items'), alias: null, arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ FieldNode( name: NameNode(value: 'type'), alias: null, arguments: [], directives: [], selectionSet: null, ) ]), ) ]), ) ]), ) ]); class ClientEventsDataQuery extends GraphQLQuery { ClientEventsDataQuery(); @override final DocumentNode document = CLIENT_EVENTS_DATA_QUERY_DOCUMENT; @override final String operationName = CLIENT_EVENTS_DATA_QUERY_DOCUMENT_OPERATION_NAME; @override List get props => [document, operationName]; @override ClientEventsData$Query parse(Map json) => ClientEventsData$Query.fromJson(json); } '''; ================================================ FILE: test/query_generator/nnbd_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On nnbd', () { test( 'On field selection', () async => testGenerator( query: 'query { nonNullAndSelected, nullableAndSelected }', schema: r''' type Query { nonNullAndSelected: String! nonNullAndNotSelected: String! nullableAndSelected: String nullableAndNotSelected: String } ''', libraryDefinition: libraryDefinition, generatedFile: output, generateHelpers: false, ), ); }); test( 'On lists and nullability', () async => testGenerator( query: 'query { i, inn, li, linn, lnni, lnninn, matrix, matrixnn }', schema: r''' type Query { i: Int inn: Int! li: [Int] linn: [Int!] lnni: [Int]! lnninn: [Int!]! matrix: [[Int]] matrixnn: [[Int!]!]! } ''', libraryDefinition: listsLibraryDefinition, generatedFile: listsOutput, generateHelpers: false, ), ); } final libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_Query'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_Query'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'nonNullAndSelected'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'nullableAndSelected'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const output = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$Query extends JsonSerializable with EquatableMixin { Query$Query(); factory Query$Query.fromJson(Map json) => _$Query$QueryFromJson(json); late String nonNullAndSelected; String? nullableAndSelected; @override List get props => [nonNullAndSelected, nullableAndSelected]; @override Map toJson() => _$Query$QueryToJson(this); } '''; final listsLibraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_Query'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_Query'), properties: [ ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int', isNonNull: true), name: ClassPropertyName(name: r'inn'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: false), name: ClassPropertyName(name: r'li'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'int', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'linn'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: true), name: ClassPropertyName(name: r'lnni'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'int', isNonNull: true), isNonNull: true), name: ClassPropertyName(name: r'lnninn'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: ListOfTypeName( typeName: DartTypeName(name: r'int'), isNonNull: false), isNonNull: false), name: ClassPropertyName(name: r'matrix'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: ListOfTypeName( typeName: DartTypeName(name: r'int', isNonNull: true), isNonNull: true), isNonNull: true), name: ClassPropertyName(name: r'matrixnn'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const listsOutput = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$Query extends JsonSerializable with EquatableMixin { Query$Query(); factory Query$Query.fromJson(Map json) => _$Query$QueryFromJson(json); int? i; late int inn; List? li; List? linn; late List lnni; late List lnninn; List?>? matrix; late List> matrixnn; @override List get props => [i, inn, li, linn, lnni, lnninn, matrix, matrixnn]; @override Map toJson() => _$Query$QueryToJson(this); } '''; ================================================ FILE: test/query_generator/query_generator_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On query generation', () { test( 'A simple query yields simple classes', () async => testGenerator( query: 'query some_query { s, i }', schema: r''' schema { query: SomeObject } type SomeObject { s: String i: Int } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_SomeObject'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$SomeObject(); factory SomeQuery$SomeObject.fromJson(Map json) => _$SomeQuery$SomeObjectFromJson(json); String? s; int? i; @override List get props => [s, i]; @override Map toJson() => _$SomeQuery$SomeObjectToJson(this); } ''', generateHelpers: false)); test( 'The selection from query can nest', () async => testGenerator( query: r''' query some_query { s o { st ob { str } } } ''', schema: r''' schema { query: Result } type Result { s: String o: SomeObject } type SomeObject { st: String ob: [AnotherObject] } type AnotherObject { str: String } ''', libraryDefinition: LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_Result'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName( name: r'SomeQuery$_Result$_SomeObject$_AnotherObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'str'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_Result$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'st'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'SomeQuery$_Result$_SomeObject$_AnotherObject'), isNonNull: false), name: ClassPropertyName(name: r'ob'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_Result'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: TypeName( name: r'SomeQuery$_Result$_SomeObject'), name: ClassPropertyName(name: r'o'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]), generatedFile: r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$Result$SomeObject$AnotherObject extends JsonSerializable with EquatableMixin { SomeQuery$Result$SomeObject$AnotherObject(); factory SomeQuery$Result$SomeObject$AnotherObject.fromJson( Map json) => _$SomeQuery$Result$SomeObject$AnotherObjectFromJson(json); String? str; @override List get props => [str]; @override Map toJson() => _$SomeQuery$Result$SomeObject$AnotherObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$Result$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$Result$SomeObject(); factory SomeQuery$Result$SomeObject.fromJson(Map json) => _$SomeQuery$Result$SomeObjectFromJson(json); String? st; List? ob; @override List get props => [st, ob]; @override Map toJson() => _$SomeQuery$Result$SomeObjectToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$Result extends JsonSerializable with EquatableMixin { SomeQuery$Result(); factory SomeQuery$Result.fromJson(Map json) => _$SomeQuery$ResultFromJson(json); String? s; SomeQuery$Result$SomeObject? o; @override List get props => [s, o]; @override Map toJson() => _$SomeQuery$ResultToJson(this); } ''', generateHelpers: false)); }); } ================================================ FILE: test/query_generator/scalars/custom_scalars_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On scalars', () { group('On custom scalars', () { test( 'If they can be converted to a simple dart class', () async => testGenerator( query: 'query query { a, b }', schema: r''' scalar MyUuid scalar Json schema { query: SomeObject } type SomeObject { a: MyUuid b: Json } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: { 'scalar_mapping': [ { 'graphql_type': 'MyUuid', 'dart_type': 'String', }, { 'graphql_type': 'Json', 'dart_type': 'Map', }, ], }, ), ); }); test( 'When they need custom parser functions', () async => testGenerator( query: 'query query { a }', schema: r''' scalar MyUuid schema { query: SomeObject } type SomeObject { a: MyUuid } ''', libraryDefinition: libraryDefinitionWithCustomParserFns, generatedFile: generatedFileWithCustomParserFns, builderOptionsMap: { 'scalar_mapping': [ { 'graphql_type': 'MyUuid', 'custom_parser_import': 'package:example/src/custom_parser.dart', 'dart_type': 'MyDartUuid', }, ], }, ), ); test( 'When they need custom imports', () async => testGenerator( query: 'query query { a, b, c, d, e, f }', schema: r''' scalar MyUuid schema { query: SomeObject } type SomeObject { a: MyUuid b: MyUuid! c: [MyUuid!]! d: [MyUuid] e: [MyUuid]! f: [MyUuid!] } ''', libraryDefinition: libraryDefinitionWithCustomImports, generatedFile: generatedFileWithCustomImports, builderOptionsMap: { 'scalar_mapping': [ { 'graphql_type': 'MyUuid', 'custom_parser_import': 'package:example/src/custom_parser.dart', 'dart_type': { 'name': 'MyUuid', 'imports': ['package:uuid/uuid.dart'], } }, ], }, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_SomeObject'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'a'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'Map'), name: ClassPropertyName(name: r'b'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); final LibraryDefinition libraryDefinitionWithCustomParserFns = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_SomeObject'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'MyDartUuid'), name: ClassPropertyName(name: r'a'), annotations: [ r'JsonKey(fromJson: fromGraphQLMyUuidNullableToDartMyDartUuidNullable, toJson: fromDartMyDartUuidNullableToGraphQLMyUuidNullable)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ], customImports: [ r'package:example/src/custom_parser.dart' ]); final LibraryDefinition libraryDefinitionWithCustomImports = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_SomeObject'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'MyUuid'), name: ClassPropertyName(name: r'a'), annotations: [ r'JsonKey(fromJson: fromGraphQLMyUuidNullableToDartMyUuidNullable, toJson: fromDartMyUuidNullableToGraphQLMyUuidNullable)' ], isResolveType: false), ClassProperty( type: DartTypeName(name: r'MyUuid', isNonNull: true), name: ClassPropertyName(name: r'b'), annotations: [ r'JsonKey(fromJson: fromGraphQLMyUuidToDartMyUuid, toJson: fromDartMyUuidToGraphQLMyUuid)' ], isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'MyUuid', isNonNull: true), isNonNull: true), name: ClassPropertyName(name: r'c'), annotations: [ r'JsonKey(fromJson: fromGraphQLListMyUuidToDartListMyUuid, toJson: fromDartListMyUuidToGraphQLListMyUuid)' ], isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'MyUuid'), isNonNull: false), name: ClassPropertyName(name: r'd'), annotations: [ r'JsonKey(fromJson: fromGraphQLListNullableMyUuidNullableToDartListNullableMyUuidNullable, toJson: fromDartListNullableMyUuidNullableToGraphQLListNullableMyUuidNullable)' ], isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'MyUuid'), isNonNull: true), name: ClassPropertyName(name: r'e'), annotations: [ r'JsonKey(fromJson: fromGraphQLListMyUuidNullableToDartListMyUuidNullable, toJson: fromDartListMyUuidNullableToGraphQLListMyUuidNullable)' ], isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: DartTypeName(name: r'MyUuid', isNonNull: true), isNonNull: false), name: ClassPropertyName(name: r'f'), annotations: [ r'JsonKey(fromJson: fromGraphQLListNullableMyUuidToDartListNullableMyUuid, toJson: fromDartListNullableMyUuidToGraphQLListNullableMyUuid)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ], customImports: [ r'package:uuid/uuid.dart', r'package:example/src/custom_parser.dart' ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$SomeObject extends JsonSerializable with EquatableMixin { Query$SomeObject(); factory Query$SomeObject.fromJson(Map json) => _$Query$SomeObjectFromJson(json); String? a; Map? b; @override List get props => [a, b]; @override Map toJson() => _$Query$SomeObjectToJson(this); } '''; const generatedFileWithCustomParserFns = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; import 'package:example/src/custom_parser.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$SomeObject extends JsonSerializable with EquatableMixin { Query$SomeObject(); factory Query$SomeObject.fromJson(Map json) => _$Query$SomeObjectFromJson(json); @JsonKey( fromJson: fromGraphQLMyUuidNullableToDartMyDartUuidNullable, toJson: fromDartMyDartUuidNullableToGraphQLMyUuidNullable) MyDartUuid? a; @override List get props => [a]; @override Map toJson() => _$Query$SomeObjectToJson(this); } '''; const generatedFileWithCustomImports = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; import 'package:uuid/uuid.dart'; import 'package:example/src/custom_parser.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$SomeObject extends JsonSerializable with EquatableMixin { Query$SomeObject(); factory Query$SomeObject.fromJson(Map json) => _$Query$SomeObjectFromJson(json); @JsonKey( fromJson: fromGraphQLMyUuidNullableToDartMyUuidNullable, toJson: fromDartMyUuidNullableToGraphQLMyUuidNullable) MyUuid? a; @JsonKey( fromJson: fromGraphQLMyUuidToDartMyUuid, toJson: fromDartMyUuidToGraphQLMyUuid) late MyUuid b; @JsonKey( fromJson: fromGraphQLListMyUuidToDartListMyUuid, toJson: fromDartListMyUuidToGraphQLListMyUuid) late List c; @JsonKey( fromJson: fromGraphQLListNullableMyUuidNullableToDartListNullableMyUuidNullable, toJson: fromDartListNullableMyUuidNullableToGraphQLListNullableMyUuidNullable) List? d; @JsonKey( fromJson: fromGraphQLListMyUuidNullableToDartListMyUuidNullable, toJson: fromDartListMyUuidNullableToGraphQLListMyUuidNullable) late List e; @JsonKey( fromJson: fromGraphQLListNullableMyUuidToDartListNullableMyUuid, toJson: fromDartListNullableMyUuidToGraphQLListNullableMyUuid) List? f; @override List get props => [a, b, c, d, e, f]; @override Map toJson() => _$Query$SomeObjectToJson(this); } '''; ================================================ FILE: test/query_generator/scalars/scalars_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On scalars', () { group('All default GraphQL scalars are parsed correctly', () { test( 'If they are defined on schema', () async => testGenerator( schema: r''' scalar Int scalar Float scalar String scalar Boolean scalar ID schema { query: SomeObject } type SomeObject { i: Int f: Float s: String b: Boolean id: ID } ''', query: 'query some_query { i, f, s, b, id }', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); test( 'All default GraphQL scalars are parsed correctly even if they are NOT explicitly defined on schema', () async => testGenerator( schema: r''' schema { query: SomeObject } type SomeObject { i: Int f: Float s: String b: Boolean id: ID } ''', query: query, libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); }); } final String query = 'query some_query { i, f, s, b, id }'; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_SomeObject'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'i'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'double'), name: ClassPropertyName(name: r'f'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r's'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'bool'), name: ClassPropertyName(name: r'b'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'id'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$SomeObject(); factory SomeQuery$SomeObject.fromJson(Map json) => _$SomeQuery$SomeObjectFromJson(json); int? i; double? f; String? s; bool? b; String? id; @override List get props => [i, f, s, b, id]; @override Map toJson() => _$SomeQuery$SomeObjectToJson(this); } '''; ================================================ FILE: test/query_generator/scalars/unused_custom_scalars_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { group('On unused custom scalars', () { test( 'They will not throw if not configured and unused', () async => testGenerator( query: 'query query { a }', schema: r''' scalar MyUuid scalar OtherScalar schema { query: SomeObject } type SomeObject { a: MyUuid b: OtherScalar } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, builderOptionsMap: { 'scalar_mapping': [ { 'graphql_type': 'MyUuid', 'dart_type': 'String', }, ], }, ), ); }); } final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'Query$_SomeObject'), operationName: r'query', classes: [ ClassDefinition( name: ClassName(name: r'Query$_SomeObject'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'a'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class Query$SomeObject extends JsonSerializable with EquatableMixin { Query$SomeObject(); factory Query$SomeObject.fromJson(Map json) => _$Query$SomeObjectFromJson(json); String? a; @override List get props => [a]; @override Map toJson() => _$Query$SomeObjectToJson(this); } '''; ================================================ FILE: test/query_generator/subscription_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:artemis/generator/data/enum_value_definition.dart'; import 'package:test/test.dart'; import '../helpers.dart'; void main() { group('On subscription', () { test( 'Should process subscription', () async => testGenerator( query: query, schema: r''' schema { query: Query subscription: Subscription } type Call { callID: CallID! localAddress: String! remoteAddress: String! callState: CallState! } enum CallState { Offered Ringing Connecting Connected Initiated Ringback Disconnected } type User { firstName: String! lastName: String! middleName: String userType: UserType! } type CallID { id: Int! } enum UserType { UserManager UserTechLead UserDev UserQA } type Query { user(userID: String!, midName: String): User! allCalls: [Call!]! } type Subscription { newUser: User! } ''', libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); }); } const query = r''' subscription NewUserSub { newUser { firstName lastName userType } } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'NewUserSub$_Subscription'), operationName: r'NewUserSub', classes: [ EnumDefinition(name: EnumName(name: r'UserType'), values: [ EnumValueDefinition(name: EnumValueName(name: r'UserManager')), EnumValueDefinition(name: EnumValueName(name: r'UserTechLead')), EnumValueDefinition(name: EnumValueName(name: r'UserDev')), EnumValueDefinition(name: EnumValueName(name: r'UserQA')), EnumValueDefinition(name: EnumValueName(name: r'ARTEMIS_UNKNOWN')) ]), ClassDefinition( name: ClassName(name: r'NewUserSub$_Subscription$_User'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'firstName'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'lastName'), isResolveType: false), ClassProperty( type: TypeName(name: r'UserType', isNonNull: true), name: ClassPropertyName(name: r'userType'), annotations: [ r'JsonKey(unknownEnumValue: UserType.artemisUnknown)' ], isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'NewUserSub$_Subscription'), properties: [ ClassProperty( type: TypeName( name: r'NewUserSub$_Subscription$_User', isNonNull: true), name: ClassPropertyName(name: r'newUser'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Subscription') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class NewUserSub$Subscription$User extends JsonSerializable with EquatableMixin { NewUserSub$Subscription$User(); factory NewUserSub$Subscription$User.fromJson(Map json) => _$NewUserSub$Subscription$UserFromJson(json); late String firstName; late String lastName; @JsonKey(unknownEnumValue: UserType.artemisUnknown) late UserType userType; @override List get props => [firstName, lastName, userType]; @override Map toJson() => _$NewUserSub$Subscription$UserToJson(this); } @JsonSerializable(explicitToJson: true) class NewUserSub$Subscription extends JsonSerializable with EquatableMixin { NewUserSub$Subscription(); factory NewUserSub$Subscription.fromJson(Map json) => _$NewUserSub$SubscriptionFromJson(json); late NewUserSub$Subscription$User newUser; @override List get props => [newUser]; @override Map toJson() => _$NewUserSub$SubscriptionToJson(this); } enum UserType { @JsonValue('UserManager') userManager, @JsonValue('UserTechLead') userTechLead, @JsonValue('UserDev') userDev, @JsonValue('UserQA') userQA, @JsonValue('ARTEMIS_UNKNOWN') artemisUnknown, } '''; ================================================ FILE: test/query_generator/union/union_types_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { test( 'On union types', () async => testGenerator( query: query, schema: graphQLSchema, libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); } final String query = r''' query some_query { o { __typename, ... on TypeA { a _ _a _a_a _a_a_ _new __typename, }, ... on TypeB { b _ _b _b_b _b_b_ new IN __typename, } } } '''; final String graphQLSchema = ''' schema { query: SomeObject } type SomeObject { o: SomeUnion } union SomeUnion = TypeA | TypeB type TypeA { a: Int _: String _a: String _a_a: String _a_a_: String _new: String } type TypeB { b: Int _: String _b: String _b_b: String _b_b_: String new: String IN: String } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'SomeQuery$_SomeObject'), operationName: r'some_query', classes: [ ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion$_TypeA'), properties: [ ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'a'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_'), annotations: [r'''JsonKey(name: '_')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_a'), annotations: [r'''JsonKey(name: '_a')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_a_a'), annotations: [r'''JsonKey(name: '_a_a')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_a_a_'), annotations: [r'''JsonKey(name: '_a_a_')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_new'), annotations: [r'''JsonKey(name: '_new')'''], isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], extension: ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion$_TypeB'), properties: [ ClassProperty( type: DartTypeName(name: r'int'), name: ClassPropertyName(name: r'b'), isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_'), annotations: [r'''JsonKey(name: '_')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_b'), annotations: [r'''JsonKey(name: '_b')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_b_b'), annotations: [r'''JsonKey(name: '_b_b')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'_b_b_'), annotations: [r'''JsonKey(name: '_b_b_')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'new'), annotations: [r'''JsonKey(name: 'new')'''], isResolveType: false), ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'IN'), annotations: [r'''JsonKey(name: 'IN')'''], isResolveType: false), ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], extension: ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion'), properties: [ ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: { r'TypeA': ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion$_TypeA'), r'TypeB': ClassName(name: r'SomeQuery$_SomeObject$_SomeUnion$_TypeB') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'SomeQuery$_SomeObject'), properties: [ ClassProperty( type: TypeName(name: r'SomeQuery$_SomeObject$_SomeUnion'), name: ClassPropertyName(name: r'o'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject$SomeUnion$TypeA extends SomeQuery$SomeObject$SomeUnion with EquatableMixin { SomeQuery$SomeObject$SomeUnion$TypeA(); factory SomeQuery$SomeObject$SomeUnion$TypeA.fromJson( Map json) => _$SomeQuery$SomeObject$SomeUnion$TypeAFromJson(json); int? a; @JsonKey(name: '_') String? $; @JsonKey(name: '_a') String? $a; @JsonKey(name: '_a_a') String? $aA; @JsonKey(name: '_a_a_') String? $aA_; @JsonKey(name: '_new') String? $new; @JsonKey(name: '__typename') @override String? $$typename; @override List get props => [a, $, $a, $aA, $aA_, $new, $$typename]; @override Map toJson() => _$SomeQuery$SomeObject$SomeUnion$TypeAToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject$SomeUnion$TypeB extends SomeQuery$SomeObject$SomeUnion with EquatableMixin { SomeQuery$SomeObject$SomeUnion$TypeB(); factory SomeQuery$SomeObject$SomeUnion$TypeB.fromJson( Map json) => _$SomeQuery$SomeObject$SomeUnion$TypeBFromJson(json); int? b; @JsonKey(name: '_') String? $; @JsonKey(name: '_b') String? $b; @JsonKey(name: '_b_b') String? $bB; @JsonKey(name: '_b_b_') String? $bB_; @JsonKey(name: 'new') String? kw$new; @JsonKey(name: 'IN') String? kw$IN; @JsonKey(name: '__typename') @override String? $$typename; @override List get props => [b, $, $b, $bB, $bB_, kw$new, kw$IN, $$typename]; @override Map toJson() => _$SomeQuery$SomeObject$SomeUnion$TypeBToJson(this); } @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject$SomeUnion extends JsonSerializable with EquatableMixin { SomeQuery$SomeObject$SomeUnion(); factory SomeQuery$SomeObject$SomeUnion.fromJson(Map json) { switch (json['__typename'].toString()) { case r'TypeA': return SomeQuery$SomeObject$SomeUnion$TypeA.fromJson(json); case r'TypeB': return SomeQuery$SomeObject$SomeUnion$TypeB.fromJson(json); default: } return _$SomeQuery$SomeObject$SomeUnionFromJson(json); } @JsonKey(name: '__typename') String? $$typename; @override List get props => [$$typename]; @override Map toJson() { switch ($$typename) { case r'TypeA': return (this as SomeQuery$SomeObject$SomeUnion$TypeA).toJson(); case r'TypeB': return (this as SomeQuery$SomeObject$SomeUnion$TypeB).toJson(); default: } return _$SomeQuery$SomeObject$SomeUnionToJson(this); } } @JsonSerializable(explicitToJson: true) class SomeQuery$SomeObject extends JsonSerializable with EquatableMixin { SomeQuery$SomeObject(); factory SomeQuery$SomeObject.fromJson(Map json) => _$SomeQuery$SomeObjectFromJson(json); SomeQuery$SomeObject$SomeUnion? o; @override List get props => [o]; @override Map toJson() => _$SomeQuery$SomeObjectToJson(this); } '''; ================================================ FILE: test/query_generator/union/union_with_nested_types_test.dart ================================================ import 'package:artemis/generator/data/data.dart'; import 'package:test/test.dart'; import '../../helpers.dart'; void main() { test( 'On union with nested types', () async => testGenerator( query: query, schema: graphQLSchema, libraryDefinition: libraryDefinition, generatedFile: generatedFile, ), ); } final String query = r''' query checkoutById($checkoutId: ID!) { node(id: $checkoutId) { __typename ...on Checkout { id lineItems { id edges { edges { id } } } } } } '''; final String graphQLSchema = ''' schema { query: QueryRoot } interface Node { id: ID! } type Checkout implements Node { id: ID! lineItems: CheckoutLineItemConnection! } type CheckoutLineItem implements Node { id: ID! } type CheckoutLineItemConnection { id: ID! edges: [CheckoutLineItemEdge!]! } type CheckoutLineItemEdge { id: ID! edges: [ImageConnection] node: CheckoutLineItem! } type Image { id: ID } type ImageConnection { id: ID edges: [ImageEdge!]! } type ImageEdge { id: ID! node: Image! } type QueryRoot { node( id: ID! ): Node } '''; final LibraryDefinition libraryDefinition = LibraryDefinition(basename: r'query.graphql', queries: [ QueryDefinition( name: QueryName(name: r'CheckoutById$_QueryRoot'), operationName: r'checkoutById', classes: [ ClassDefinition( name: ClassName( name: r'CheckoutById$_QueryRoot$_Node$_Checkout$_CheckoutLineItemConnection$_CheckoutLineItemEdge$_ImageConnection'), properties: [ ClassProperty( type: DartTypeName(name: r'String'), name: ClassPropertyName(name: r'id'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName( name: r'CheckoutById$_QueryRoot$_Node$_Checkout$_CheckoutLineItemConnection$_CheckoutLineItemEdge'), properties: [ ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'CheckoutById$_QueryRoot$_Node$_Checkout$_CheckoutLineItemConnection$_CheckoutLineItemEdge$_ImageConnection'), isNonNull: false), name: ClassPropertyName(name: r'edges'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName( name: r'CheckoutById$_QueryRoot$_Node$_Checkout$_CheckoutLineItemConnection'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: ListOfTypeName( typeName: TypeName( name: r'CheckoutById$_QueryRoot$_Node$_Checkout$_CheckoutLineItemConnection$_CheckoutLineItemEdge', isNonNull: true), isNonNull: true), name: ClassPropertyName(name: r'edges'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CheckoutById$_QueryRoot$_Node$_Checkout'), properties: [ ClassProperty( type: DartTypeName(name: r'String', isNonNull: true), name: ClassPropertyName(name: r'id'), isResolveType: false), ClassProperty( type: TypeName( name: r'CheckoutById$_QueryRoot$_Node$_Checkout$_CheckoutLineItemConnection', isNonNull: true), name: ClassPropertyName(name: r'lineItems'), isResolveType: false) ], extension: ClassName(name: r'CheckoutById$_QueryRoot$_Node'), factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CheckoutById$_QueryRoot$_Node'), properties: [ ClassProperty( type: TypeName(name: r'String'), name: ClassPropertyName(name: r'__typename'), annotations: [r'''JsonKey(name: '__typename')'''], isResolveType: true) ], factoryPossibilities: { r'Checkout': ClassName(name: r'CheckoutById$_QueryRoot$_Node$_Checkout') }, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false), ClassDefinition( name: ClassName(name: r'CheckoutById$_QueryRoot'), properties: [ ClassProperty( type: TypeName(name: r'CheckoutById$_QueryRoot$_Node'), name: ClassPropertyName(name: r'node'), isResolveType: false) ], factoryPossibilities: {}, typeNameField: ClassPropertyName(name: r'__typename'), isInput: false) ], inputs: [ QueryInput( type: DartTypeName(name: r'String', isNonNull: true), name: QueryInputName(name: r'checkoutId')) ], generateHelpers: false, suffix: r'Query') ]); const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import 'package:gql/ast.dart'; part 'query.graphql.g.dart'; @JsonSerializable(explicitToJson: true) class CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge$ImageConnection extends JsonSerializable with EquatableMixin { CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge$ImageConnection(); factory CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge$ImageConnection.fromJson( Map json) => _$CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge$ImageConnectionFromJson( json); String? id; @override List get props => [id]; @override Map toJson() => _$CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge$ImageConnectionToJson( this); } @JsonSerializable(explicitToJson: true) class CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge extends JsonSerializable with EquatableMixin { CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge(); factory CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge.fromJson( Map json) => _$CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdgeFromJson( json); List? edges; @override List get props => [edges]; @override Map toJson() => _$CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdgeToJson( this); } @JsonSerializable(explicitToJson: true) class CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection extends JsonSerializable with EquatableMixin { CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection(); factory CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection.fromJson( Map json) => _$CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnectionFromJson( json); late String id; late List< CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection$CheckoutLineItemEdge> edges; @override List get props => [id, edges]; @override Map toJson() => _$CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnectionToJson( this); } @JsonSerializable(explicitToJson: true) class CheckoutById$QueryRoot$Node$Checkout extends CheckoutById$QueryRoot$Node with EquatableMixin { CheckoutById$QueryRoot$Node$Checkout(); factory CheckoutById$QueryRoot$Node$Checkout.fromJson( Map json) => _$CheckoutById$QueryRoot$Node$CheckoutFromJson(json); late String id; late CheckoutById$QueryRoot$Node$Checkout$CheckoutLineItemConnection lineItems; @override List get props => [id, lineItems]; @override Map toJson() => _$CheckoutById$QueryRoot$Node$CheckoutToJson(this); } @JsonSerializable(explicitToJson: true) class CheckoutById$QueryRoot$Node extends JsonSerializable with EquatableMixin { CheckoutById$QueryRoot$Node(); factory CheckoutById$QueryRoot$Node.fromJson(Map json) { switch (json['__typename'].toString()) { case r'Checkout': return CheckoutById$QueryRoot$Node$Checkout.fromJson(json); default: } return _$CheckoutById$QueryRoot$NodeFromJson(json); } @JsonKey(name: '__typename') String? $$typename; @override List get props => [$$typename]; @override Map toJson() { switch ($$typename) { case r'Checkout': return (this as CheckoutById$QueryRoot$Node$Checkout).toJson(); default: } return _$CheckoutById$QueryRoot$NodeToJson(this); } } @JsonSerializable(explicitToJson: true) class CheckoutById$QueryRoot extends JsonSerializable with EquatableMixin { CheckoutById$QueryRoot(); factory CheckoutById$QueryRoot.fromJson(Map json) => _$CheckoutById$QueryRootFromJson(json); CheckoutById$QueryRoot$Node? node; @override List get props => [node]; @override Map toJson() => _$CheckoutById$QueryRootToJson(this); } '''; ================================================ FILE: tool/fetch_schema.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:args/args.dart'; import 'package:http/http.dart' as http; const String introspectionQuery = ''' query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } '''; Future fetchGraphQLSchemaStringFromURL(String graphqlEndpoint, {http.Client? client}) async { final httpClient = client ?? http.Client(); final response = await httpClient.post(Uri.parse(graphqlEndpoint), body: { 'operationName': 'IntrospectionQuery', 'query': introspectionQuery, }); return response.body; } void main(List args) async { final parser = ArgParser() ..addFlag('help', abbr: 'h', help: 'Show this help', negatable: false) ..addOption('endpoint', abbr: 'e', help: 'Endpoint to hit to get the schema') ..addOption('output', abbr: 'o', help: 'File to output the schema to'); final results = parser.parse(args); if (results['help'] as bool || args.isEmpty) { return print(parser.usage); } File( results['output'] as String, ).writeAsStringSync( await fetchGraphQLSchemaStringFromURL( results['endpoint'] as String, ), flush: true, ); }