sui_graphql_rpc/
config.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3use std::str::FromStr;
4
5use crate::functional_group::FunctionalGroup;
6use async_graphql::*;
7use fastcrypto_zkp::bn254::zk_login_api::ZkLoginEnv;
8use move_core_types::ident_str;
9use move_core_types::identifier::IdentStr;
10use serde::{Deserialize, Serialize};
11use std::{collections::BTreeSet, fmt::Display, time::Duration};
12use sui_default_config::DefaultConfig;
13use sui_name_service::NameServiceConfig;
14use sui_types::base_types::{ObjectID, SuiAddress};
15
16pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(30_000);
17pub(crate) const MAX_CONCURRENT_REQUESTS: usize = 1_000;
18
19// Move Registry constants
20pub(crate) const MOVE_REGISTRY_MODULE: &IdentStr = ident_str!("name");
21pub(crate) const MOVE_REGISTRY_TYPE: &IdentStr = ident_str!("Name");
22const MOVE_REGISTRY_PACKAGE: &str =
23    "0x62c1f5b1cb9e3bfc3dd1f73c95066487b662048a6358eabdbf67f6cdeca6db4b";
24const MOVE_REGISTRY_TABLE_ID: &str =
25    "0xe8417c530cde59eddf6dfb760e8a0e3e2c6f17c69ddaab5a73dd6a6e65fc463b";
26const DEFAULT_PAGE_LIMIT: u16 = 50;
27
28/// The combination of all configurations for the GraphQL service.
29#[DefaultConfig]
30#[derive(Clone, Default, Debug)]
31pub struct ServerConfig {
32    pub service: ServiceConfig,
33    pub connection: ConnectionConfig,
34    pub internal_features: InternalFeatureConfig,
35    pub tx_exec_full_node: TxExecFullNodeConfig,
36    pub ide: Ide,
37}
38
39/// Configuration for connections for the RPC, passed in as command-line arguments. This configures
40/// specific connections between this service and other services, and might differ from instance to
41/// instance of the GraphQL service.
42#[DefaultConfig]
43#[derive(clap::Args, Clone, Eq, PartialEq, Debug)]
44pub struct ConnectionConfig {
45    /// Port to bind the server to
46    #[clap(short, long, default_value_t = ConnectionConfig::default().port)]
47    pub port: u16,
48    /// Host to bind the server to
49    #[clap(long, default_value_t = ConnectionConfig::default().host)]
50    pub host: String,
51    /// DB URL for data fetching
52    #[clap(short, long, default_value_t = ConnectionConfig::default().db_url)]
53    pub db_url: String,
54    /// Pool size for DB connections
55    #[clap(long, default_value_t = ConnectionConfig::default().db_pool_size)]
56    pub db_pool_size: u32,
57    /// Host to bind the prom server to
58    #[clap(long, default_value_t = ConnectionConfig::default().prom_host)]
59    pub prom_host: String,
60    /// Port to bind the prom server to
61    #[clap(long, default_value_t = ConnectionConfig::default().prom_port)]
62    pub prom_port: u16,
63    /// Skip checking whether the service is compatible with the DB it is about to connect to, on
64    /// start-up.
65    #[clap(long, default_value_t = ConnectionConfig::default().skip_migration_consistency_check)]
66    pub skip_migration_consistency_check: bool,
67}
68
69/// Configuration on features supported by the GraphQL service, passed in a TOML-based file. These
70/// configurations are shared across fleets of the service, i.e. all testnet services will have the
71/// same `ServiceConfig`.
72#[DefaultConfig]
73#[derive(Clone, Default, Eq, PartialEq, Debug)]
74pub struct ServiceConfig {
75    pub limits: Limits,
76    pub disabled_features: BTreeSet<FunctionalGroup>,
77    pub experiments: Experiments,
78    pub name_service: NameServiceConfig,
79    pub background_tasks: BackgroundTasksConfig,
80    pub zklogin: ZkLoginConfig,
81    pub move_registry: MoveRegistryConfig,
82}
83
84#[DefaultConfig]
85#[derive(Clone, Eq, PartialEq, Debug)]
86pub struct Limits {
87    /// Maximum depth of nodes in the requests.
88    pub max_query_depth: u32,
89    /// Maximum number of nodes in the requests.
90    pub max_query_nodes: u32,
91    /// Maximum number of output nodes allowed in the response.
92    pub max_output_nodes: u32,
93    /// Maximum size in bytes allowed for the `txBytes` and `signatures` fields of a GraphQL
94    /// mutation request in the `executeTransactionBlock` node, and for the `txBytes` of a
95    /// `dryRunTransactionBlock` node.
96    pub max_tx_payload_size: u32,
97    /// Maximum size in bytes of the JSON payload of a GraphQL read request (excluding
98    /// `max_tx_payload_size`).
99    pub max_query_payload_size: u32,
100    /// Queries whose EXPLAIN cost are more than this will be logged. Given in the units used by the
101    /// database (where 1.0 is roughly the cost of a sequential page access).
102    pub max_db_query_cost: u32,
103    /// Paginated queries will return this many elements if a page size is not provided.
104    pub default_page_size: u32,
105    /// Paginated queries can return at most this many elements.
106    pub max_page_size: u32,
107    /// Time (in milliseconds) to wait for a transaction to be executed and the results returned
108    /// from GraphQL. If the transaction takes longer than this time to execute, the request will
109    /// return a timeout error, but the transaction may continue executing.
110    pub mutation_timeout_ms: u32,
111    /// Time (in milliseconds) to wait for a read request from the GraphQL service. Requests that
112    /// take longer than this time to return a result will return a timeout error.
113    pub request_timeout_ms: u32,
114    /// Maximum amount of nesting among type arguments (type arguments nest when a type argument is
115    /// itself generic and has arguments).
116    pub max_type_argument_depth: u32,
117    /// Maximum number of type parameters a type can have.
118    pub max_type_argument_width: u32,
119    /// Maximum size of a fully qualified type.
120    pub max_type_nodes: u32,
121    /// Maximum depth of a move value.
122    pub max_move_value_depth: u32,
123    /// Maximum number of transaction ids that can be passed to a `TransactionBlockFilter`.
124    pub max_transaction_ids: u32,
125    /// Maximum number of keys that can be passed to a `multiGetObjects` query.
126    pub max_multi_get_objects_keys: u32,
127    /// Maximum number of candidates to scan when gathering a page of results.
128    pub max_scan_limit: u32,
129}
130
131#[DefaultConfig]
132#[derive(Copy, Clone, Eq, PartialEq, Debug)]
133pub struct BackgroundTasksConfig {
134    /// How often the watermark task checks the indexer database to update the checkpoint and epoch
135    /// watermarks.
136    pub watermark_update_ms: u64,
137}
138
139#[DefaultConfig]
140#[derive(Clone, Eq, PartialEq, Debug)]
141pub struct MoveRegistryConfig {
142    pub(crate) external_api_url: Option<String>,
143    pub(crate) resolution_type: ResolutionType,
144    pub(crate) page_limit: u16,
145    pub(crate) package_address: SuiAddress,
146    pub(crate) registry_id: ObjectID,
147}
148
149#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
150pub(crate) enum ResolutionType {
151    Internal,
152    External,
153}
154
155/// The Version of the service. `year.month` represents the major release.
156/// New `patch` versions represent backwards compatible fixes for their major release.
157/// The `full` version is `year.month.patch-sha`.
158#[derive(Copy, Clone, Debug)]
159pub struct Version {
160    /// The major version for the release
161    pub major: &'static str,
162    /// The minor version of the release
163    pub minor: &'static str,
164    /// The patch version of the release
165    pub patch: &'static str,
166    /// The full commit SHA that the release was built from
167    pub sha: &'static str,
168    /// The full version string: {MAJOR}.{MINOR}.{PATCH}-{SHA}
169    ///
170    /// The full version is pre-computed as a &'static str because that is what is required for
171    /// `uptime_metric`.
172    pub full: &'static str,
173}
174
175impl Version {
176    /// Use for testing when you need the Version obj and a year.month &str
177    pub fn for_testing() -> Self {
178        Self {
179            major: "42",
180            minor: "43",
181            patch: "44",
182            sha: "testing-no-sha",
183            // note that this full field is needed for metrics but not for testing
184            full: "42.43.44-testing-no-sha",
185        }
186    }
187}
188
189#[DefaultConfig]
190#[derive(clap::Args, Clone, Debug)]
191pub struct Ide {
192    /// The title to display at the top of the web-based GraphiQL IDE.
193    #[clap(short, long, default_value_t = Ide::default().ide_title)]
194    pub ide_title: String,
195}
196
197#[DefaultConfig]
198#[derive(Clone, Default, Eq, PartialEq, Debug)]
199pub struct Experiments {
200    // Add experimental flags here, to provide access to them through-out the GraphQL
201    // implementation.
202    #[cfg(test)]
203    test_flag: bool,
204}
205
206#[DefaultConfig]
207#[derive(Clone, Debug)]
208pub struct InternalFeatureConfig {
209    pub(crate) query_limits_checker: bool,
210    pub(crate) directive_checker: bool,
211    pub(crate) feature_gate: bool,
212    pub(crate) logger: bool,
213    pub(crate) query_timeout: bool,
214    pub(crate) metrics: bool,
215    pub(crate) tracing: bool,
216    pub(crate) apollo_tracing: bool,
217    pub(crate) open_telemetry: bool,
218}
219
220#[DefaultConfig]
221#[derive(clap::Args, Clone, Default, Debug)]
222pub struct TxExecFullNodeConfig {
223    /// RPC URL for the fullnode to send transactions to execute and dry-run.
224    #[clap(long)]
225    pub(crate) node_rpc_url: Option<String>,
226}
227
228#[DefaultConfig]
229#[derive(Clone, Default, Eq, PartialEq, Debug)]
230pub struct ZkLoginConfig {
231    pub env: ZkLoginEnv,
232}
233
234/// The enabled features and service limits configured by the server.
235#[Object]
236impl ServiceConfig {
237    /// Check whether `feature` is enabled on this GraphQL service.
238    async fn is_enabled(&self, feature: FunctionalGroup) -> bool {
239        !self.disabled_features.contains(&feature)
240    }
241
242    /// List of all features that are enabled on this GraphQL service.
243    async fn enabled_features(&self) -> Vec<FunctionalGroup> {
244        FunctionalGroup::all()
245            .iter()
246            .filter(|g| !self.disabled_features.contains(g))
247            .copied()
248            .collect()
249    }
250
251    /// The maximum depth a GraphQL query can be to be accepted by this service.
252    pub async fn max_query_depth(&self) -> u32 {
253        self.limits.max_query_depth
254    }
255
256    /// The maximum number of nodes (field names) the service will accept in a single query.
257    pub async fn max_query_nodes(&self) -> u32 {
258        self.limits.max_query_nodes
259    }
260
261    /// The maximum number of output nodes in a GraphQL response.
262    ///
263    /// Non-connection nodes have a count of 1, while connection nodes are counted as
264    /// the specified 'first' or 'last' number of items, or the default_page_size
265    /// as set by the server if those arguments are not set.
266    ///
267    /// Counts accumulate multiplicatively down the query tree. For example, if a query starts
268    /// with a connection of first: 10 and has a field to a connection with last: 20, the count
269    /// at the second level would be 200 nodes. This is then summed to the count of 10 nodes
270    /// at the first level, for a total of 210 nodes.
271    pub async fn max_output_nodes(&self) -> u32 {
272        self.limits.max_output_nodes
273    }
274
275    /// Maximum estimated cost of a database query used to serve a GraphQL request.  This is
276    /// measured in the same units that the database uses in EXPLAIN queries.
277    async fn max_db_query_cost(&self) -> u32 {
278        self.limits.max_db_query_cost
279    }
280
281    /// Default number of elements allowed on a single page of a connection.
282    async fn default_page_size(&self) -> u32 {
283        self.limits.default_page_size
284    }
285
286    /// Maximum number of elements allowed on a single page of a connection.
287    async fn max_page_size(&self) -> u32 {
288        self.limits.max_page_size
289    }
290
291    /// Maximum time in milliseconds spent waiting for a response from fullnode after issuing a
292    /// a transaction to execute. Note that the transaction may still succeed even in the case of a
293    /// timeout. Transactions are idempotent, so a transaction that times out should be resubmitted
294    /// until the network returns a definite response (success or failure, not timeout).
295    async fn mutation_timeout_ms(&self) -> u32 {
296        self.limits.mutation_timeout_ms
297    }
298
299    /// Maximum time in milliseconds that will be spent to serve one query request.
300    async fn request_timeout_ms(&self) -> u32 {
301        self.limits.request_timeout_ms
302    }
303
304    /// The maximum bytes allowed for the `txBytes` and `signatures` fields of the GraphQL mutation
305    /// `executeTransactionBlock` node, or for the `txBytes` of a `dryRunTransactionBlock`.
306    ///
307    /// It is the value of the maximum transaction bytes (including the signatures) allowed by the
308    /// protocol, plus the Base64 overhead (roughly 1/3 of the original string).
309    async fn max_transaction_payload_size(&self) -> u32 {
310        self.limits.max_tx_payload_size
311    }
312
313    /// The maximum bytes allowed for the JSON object in the request body of a GraphQL query, for
314    /// the read part of the query.
315    /// In case of mutations or dryRunTransactionBlocks the txBytes and signatures are not
316    /// included in this limit.
317    async fn max_query_payload_size(&self) -> u32 {
318        self.limits.max_query_payload_size
319    }
320
321    /// Maximum nesting allowed in type arguments in Move Types resolved by this service.
322    async fn max_type_argument_depth(&self) -> u32 {
323        self.limits.max_type_argument_depth
324    }
325
326    /// Maximum number of type arguments passed into a generic instantiation of a Move Type resolved
327    /// by this service.
328    async fn max_type_argument_width(&self) -> u32 {
329        self.limits.max_type_argument_width
330    }
331
332    /// Maximum number of structs that need to be processed when calculating the layout of a single
333    /// Move Type.
334    async fn max_type_nodes(&self) -> u32 {
335        self.limits.max_type_nodes
336    }
337
338    /// Maximum nesting allowed in struct fields when calculating the layout of a single Move Type.
339    async fn max_move_value_depth(&self) -> u32 {
340        self.limits.max_move_value_depth
341    }
342
343    /// Maximum number of transaction ids that can be passed to a `TransactionBlockFilter`.
344    async fn max_transaction_ids(&self) -> u32 {
345        self.limits.max_transaction_ids
346    }
347
348    /// Maximum number of keys that can be passed to a `multiGetObjects` query.
349    async fn max_multi_get_objects_keys(&self) -> u32 {
350        self.limits.max_multi_get_objects_keys
351    }
352
353    /// Maximum number of candidates to scan when gathering a page of results.
354    async fn max_scan_limit(&self) -> u32 {
355        self.limits.max_scan_limit
356    }
357}
358
359impl TxExecFullNodeConfig {
360    pub fn new(node_rpc_url: Option<String>) -> Self {
361        Self { node_rpc_url }
362    }
363}
364
365impl ConnectionConfig {
366    pub fn db_name(&self) -> String {
367        self.db_url.split('/').next_back().unwrap().to_string()
368    }
369
370    pub fn db_url(&self) -> String {
371        self.db_url.clone()
372    }
373
374    pub fn db_pool_size(&self) -> u32 {
375        self.db_pool_size
376    }
377
378    pub fn server_address(&self) -> String {
379        format!("{}:{}", self.host, self.port)
380    }
381
382    pub fn host(&self) -> String {
383        self.host.clone()
384    }
385
386    pub fn port(&self) -> u16 {
387        self.port
388    }
389}
390
391impl ServiceConfig {
392    pub fn read(contents: &str) -> Result<Self, toml::de::Error> {
393        toml::de::from_str::<Self>(contents)
394    }
395
396    pub fn test_defaults() -> Self {
397        Self {
398            background_tasks: BackgroundTasksConfig::test_defaults(),
399            zklogin: ZkLoginConfig {
400                env: ZkLoginEnv::Test,
401            },
402            ..Default::default()
403        }
404    }
405
406    pub fn move_registry_test_defaults(
407        external: bool,
408        endpoint: Option<String>,
409        pkg_address: Option<SuiAddress>,
410        object_id: Option<ObjectID>,
411        page_limit: Option<u16>,
412    ) -> Self {
413        Self {
414            move_registry: MoveRegistryConfig {
415                resolution_type: if external {
416                    ResolutionType::External
417                } else {
418                    ResolutionType::Internal
419                },
420                external_api_url: endpoint,
421                package_address: pkg_address.unwrap_or_default(),
422                registry_id: object_id.unwrap_or(ObjectID::random()),
423                page_limit: page_limit.unwrap_or(50),
424            },
425            ..Self::test_defaults()
426        }
427    }
428}
429
430impl Limits {
431    /// Extract limits for the package resolver.
432    pub fn package_resolver_limits(&self) -> sui_package_resolver::Limits {
433        sui_package_resolver::Limits {
434            max_type_argument_depth: self.max_type_argument_depth as usize,
435            max_type_argument_width: self.max_type_argument_width as usize,
436            max_type_nodes: self.max_type_nodes as usize,
437            max_move_value_depth: self.max_move_value_depth as usize,
438        }
439    }
440}
441
442impl BackgroundTasksConfig {
443    pub fn test_defaults() -> Self {
444        Self {
445            watermark_update_ms: 100, // Set to 100ms for testing
446        }
447    }
448}
449
450impl MoveRegistryConfig {
451    pub(crate) fn new(
452        resolution_type: ResolutionType,
453        external_api_url: Option<String>,
454        page_limit: u16,
455        package_address: SuiAddress,
456        registry_id: ObjectID,
457    ) -> Self {
458        Self {
459            resolution_type,
460            external_api_url,
461            page_limit,
462            package_address,
463            registry_id,
464        }
465    }
466}
467
468impl Default for Ide {
469    fn default() -> Self {
470        Self {
471            ide_title: "Sui GraphQL IDE".to_string(),
472        }
473    }
474}
475
476impl Default for ConnectionConfig {
477    fn default() -> Self {
478        Self {
479            port: 8000,
480            host: "127.0.0.1".to_string(),
481            db_url: "postgres://postgres:postgrespw@localhost:5432/sui_indexer".to_string(),
482            db_pool_size: 10,
483            prom_host: "0.0.0.0".to_string(),
484            prom_port: 9184,
485            skip_migration_consistency_check: false,
486        }
487    }
488}
489
490impl Default for Limits {
491    fn default() -> Self {
492        // Picked so that TS SDK shim layer queries all pass limit.
493        // TODO: calculate proper cost limits
494        Self {
495            max_query_depth: 20,
496            max_query_nodes: 300,
497            max_output_nodes: 100_000,
498            max_query_payload_size: 5_000,
499            max_db_query_cost: 20_000,
500            default_page_size: 20,
501            max_page_size: 50,
502            // This default was picked as the sum of pre- and post- quorum timeouts from
503            // [`sui_core::authority_aggregator::TimeoutConfig`], with a 10% buffer.
504            //
505            // <https://github.com/MystenLabs/sui/blob/eaf05fe5d293c06e3a2dfc22c87ba2aef419d8ea/crates/sui-core/src/authority_aggregator.rs#L84-L85>
506            mutation_timeout_ms: 74_000,
507            request_timeout_ms: 40_000,
508            // The following limits reflect the max values set in ProtocolConfig, at time of writing.
509            // <https://github.com/MystenLabs/sui/blob/333f87061f0656607b1928aba423fa14ca16899e/crates/sui-protocol-config/src/lib.rs#L1580>
510            max_type_argument_depth: 16,
511            // <https://github.com/MystenLabs/sui/blob/4b934f87acae862cecbcbefb3da34cabb79805aa/crates/sui-protocol-config/src/lib.rs#L1618>
512            max_type_argument_width: 32,
513            // <https://github.com/MystenLabs/sui/blob/4b934f87acae862cecbcbefb3da34cabb79805aa/crates/sui-protocol-config/src/lib.rs#L1622>
514            max_type_nodes: 256,
515            // <https://github.com/MystenLabs/sui/blob/4b934f87acae862cecbcbefb3da34cabb79805aa/crates/sui-protocol-config/src/lib.rs#L1988>
516            max_move_value_depth: 128,
517            // Filter-specific limits, such as the number of transaction ids that can be specified
518            // for the `TransactionBlockFilter`.
519            max_transaction_ids: 1000,
520            max_multi_get_objects_keys: 500,
521            max_scan_limit: 100_000_000,
522            // This value is set to be the size of the max transaction bytes allowed + base64
523            // overhead (roughly 1/3 of the original string). This is rounded up.
524            //
525            // <https://github.com/MystenLabs/sui/blob/4b934f87acae862cecbcbefb3da34cabb79805aa/crates/sui-protocol-config/src/lib.rs#L1578>
526            max_tx_payload_size: (128u32 * 1024u32 * 4u32).div_ceil(3),
527        }
528    }
529}
530
531impl Default for InternalFeatureConfig {
532    fn default() -> Self {
533        Self {
534            query_limits_checker: true,
535            directive_checker: true,
536            feature_gate: true,
537            logger: true,
538            query_timeout: true,
539            metrics: true,
540            tracing: false,
541            apollo_tracing: false,
542            open_telemetry: false,
543        }
544    }
545}
546
547impl Default for BackgroundTasksConfig {
548    fn default() -> Self {
549        Self {
550            watermark_update_ms: 500,
551        }
552    }
553}
554
555impl Display for Version {
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        write!(f, "{}", self.full)
558    }
559}
560
561// TODO: Keeping the values as is, because we'll remove the default getters
562// when we refactor to use `[GraphqlConfig]` macro.
563impl Default for MoveRegistryConfig {
564    fn default() -> Self {
565        Self::new(
566            ResolutionType::Internal,
567            None,
568            DEFAULT_PAGE_LIMIT,
569            SuiAddress::from_str(MOVE_REGISTRY_PACKAGE).unwrap(),
570            ObjectID::from_str(MOVE_REGISTRY_TABLE_ID).unwrap(),
571        )
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578
579    #[test]
580    fn test_read_empty_service_config() {
581        let actual = ServiceConfig::read("").unwrap();
582        let expect = ServiceConfig::default();
583        assert_eq!(actual, expect);
584    }
585
586    #[test]
587    fn test_read_limits_in_service_config() {
588        let actual = ServiceConfig::read(
589            r#" [limits]
590                max-query-depth = 100
591                max-query-nodes = 300
592                max-output-nodes = 200000
593                max-mutation-payload-size = 174763
594                max-query-payload-size = 2000
595                max-db-query-cost = 50
596                default-page-size = 20
597                max-page-size = 50
598                mutation-timeout-ms = 74000
599                request-timeout-ms = 27000
600                max-type-argument-depth = 32
601                max-type-argument-width = 64
602                max-type-nodes = 128
603                max-move-value-depth = 256
604                max-transaction-ids = 11
605                max-multi-get-objects-keys = 11
606                max-scan-limit = 50
607            "#,
608        )
609        .unwrap();
610
611        let expect = ServiceConfig {
612            limits: Limits {
613                max_query_depth: 100,
614                max_query_nodes: 300,
615                max_output_nodes: 200000,
616                max_tx_payload_size: 174763,
617                max_query_payload_size: 2000,
618                max_db_query_cost: 50,
619                default_page_size: 20,
620                max_page_size: 50,
621                mutation_timeout_ms: 74_000,
622                request_timeout_ms: 27_000,
623                max_type_argument_depth: 32,
624                max_type_argument_width: 64,
625                max_type_nodes: 128,
626                max_move_value_depth: 256,
627                max_transaction_ids: 11,
628                max_multi_get_objects_keys: 11,
629                max_scan_limit: 50,
630            },
631            ..Default::default()
632        };
633
634        assert_eq!(actual, expect)
635    }
636
637    #[test]
638    fn test_read_enabled_features_in_service_config() {
639        let actual = ServiceConfig::read(
640            r#" disabled-features = [
641                  "coins",
642                  "name-service",
643                ]
644            "#,
645        )
646        .unwrap();
647
648        use FunctionalGroup as G;
649        let expect = ServiceConfig {
650            disabled_features: BTreeSet::from([G::Coins, G::NameService]),
651            ..Default::default()
652        };
653
654        assert_eq!(actual, expect)
655    }
656
657    #[test]
658    fn test_read_experiments_in_service_config() {
659        let actual = ServiceConfig::read(
660            r#" [experiments]
661                test-flag = true
662            "#,
663        )
664        .unwrap();
665
666        let expect = ServiceConfig {
667            experiments: Experiments { test_flag: true },
668            ..Default::default()
669        };
670
671        assert_eq!(actual, expect)
672    }
673
674    #[test]
675    fn test_read_everything_in_service_config() {
676        let actual = ServiceConfig::read(
677            r#" disabled-features = ["analytics"]
678
679                [limits]
680                max-query-depth = 42
681                max-query-nodes = 320
682                max-output-nodes = 200000
683                max-tx-payload-size = 181017
684                max-query-payload-size = 200
685                max-db-query-cost = 20
686                default-page-size = 10
687                max-page-size = 20
688                mutation-timeout-ms = 74000
689                request-timeout-ms = 30000
690                max-type-argument-depth = 32
691                max-type-argument-width = 64
692                max-type-nodes = 128
693                max-move-value-depth = 256
694                max-transaction-ids = 42
695                max-multi-get-objects-keys = 42
696                max-scan-limit = 420
697
698                [experiments]
699                test-flag = true
700            "#,
701        )
702        .unwrap();
703
704        let expect = ServiceConfig {
705            limits: Limits {
706                max_query_depth: 42,
707                max_query_nodes: 320,
708                max_output_nodes: 200000,
709                max_tx_payload_size: 181017,
710                max_query_payload_size: 200,
711                max_db_query_cost: 20,
712                default_page_size: 10,
713                max_page_size: 20,
714                mutation_timeout_ms: 74_000,
715                request_timeout_ms: 30_000,
716                max_type_argument_depth: 32,
717                max_type_argument_width: 64,
718                max_type_nodes: 128,
719                max_move_value_depth: 256,
720                max_transaction_ids: 42,
721                max_multi_get_objects_keys: 42,
722                max_scan_limit: 420,
723            },
724            disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
725            experiments: Experiments { test_flag: true },
726            ..Default::default()
727        };
728
729        assert_eq!(actual, expect);
730    }
731
732    #[test]
733    fn test_read_partial_in_service_config() {
734        let actual = ServiceConfig::read(
735            r#" disabled-features = ["analytics"]
736
737                [limits]
738                max-query-depth = 42
739                max-query-nodes = 320
740            "#,
741        )
742        .unwrap();
743
744        // When reading partially, the other parts will come from the default implementation.
745        let expect = ServiceConfig {
746            limits: Limits {
747                max_query_depth: 42,
748                max_query_nodes: 320,
749                ..Default::default()
750            },
751            disabled_features: BTreeSet::from([FunctionalGroup::Analytics]),
752            ..Default::default()
753        };
754
755        assert_eq!(actual, expect);
756    }
757}