/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![allow(warnings, rust_2018_idioms)]

mod graphqlschema_generated;

pub use graphqlschema_generated::*;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

mod print_schema;

pub use print_schema::*;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::hash_map::DefaultHasher;
use std::collections::BTreeMap;
use std::fmt::Result as FmtResult;
use std::fmt::Write;
use std::hash::Hash;
use std::hash::Hasher;

use fnv::FnvHashMap;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use itertools::Itertools;
use schema::*;

const DEAULT_SHARD_COUNT: usize = 1;

pub fn print(schema: &SDLSchema) -> String {
    let mut builder: String = String::new();
    write!(
        builder,
        "{}{}{}",
        print_schema_definition(schema),
        print_directives(schema),
        print_types(schema)
    )
    .unwrap();
    builder
}

pub fn print_schema_definition(schema: &SDLSchema) -> String {
    if is_schema_of_common_name(schema) || !has_schema_definition_types(schema) {
        return String::new();
    }
    let mut result = vec![String::new(); DEAULT_SHARD_COUNT];
    write_schema_definition(schema, &mut result).unwrap();
    result.into_iter().next().unwrap()
}

pub fn print_directives(schema: &SDLSchema) -> String {
    let mut result = vec![String::new(); DEAULT_SHARD_COUNT];
    write_directives(schema, &mut result).unwrap();
    result.into_iter().next().unwrap()
}

pub fn print_directive(schema: &SDLSchema, directive: &Directive) -> String {
    let mut result = vec![String::new(); DEAULT_SHARD_COUNT];
    write_directive(schema, &mut result, directive).unwrap();
    result.into_iter().next().unwrap()
}

pub fn print_types(schema: &SDLSchema) -> String {
    let mut result = vec![String::new(); DEAULT_SHARD_COUNT];
    write_types(schema, &mut result).unwrap();
    result.into_iter().next().unwrap()
}

/// Returns a sharded GraphQL SDLSchema
///
/// # Arguments
///
/// * `schema` - GraphQL SDLSchema
///
/// * `shard_count` - Total shard count. Returned vec will have this size.
///
/// * `type_shard_count` - To further shard a single type, provide this.
/// For e.g you might want to shard Query type because its huge.
/// Sum of all the shard counts provided here must be less than shard_count param.
pub fn print_types_directives_as_shards(
    schema: &SDLSchema,
    shard_count: usize,
    type_shard_count: FnvHashMap<StringKey, usize>,
) -> Vec<String> {
    let typeshard_count: usize = type_shard_count.values().sum();
    if typeshard_count >= shard_count {
        panic!(
            "Total shard count:{} must be greater than sum of all shard counts:{} for inidividual types",
            shard_count, typeshard_count
        );
    }
    let mut shards: Vec<String> = vec![String::new(); shard_count - typeshard_count];

    // Print directives to first shard
    shards
        .first_mut()
        .unwrap()
        .push_str(&print_directives(schema));

    let mut type_shards: FnvHashMap<StringKey, Vec<String>> = type_shard_count
        .iter()
        .map(|(type_name, count)| (*type_name, vec![String::new(); *count]))
        .collect();
    write_types_as_shards(
        schema,
        &mut shards,
        shard_count - typeshard_count,
        &mut type_shards,
    )
    .unwrap();
    type_shards
        .iter_mut()
        .sorted_by_key(|(type_name, _shards)| type_name.lookup())
        .for_each(|(_type_name, type_shards)| shards.append(type_shards));
    shards
}

pub fn print_type(schema: &SDLSchema, type_: Type) -> String {
    let mut result = vec![String::new(); DEAULT_SHARD_COUNT];
    write_type(schema, &mut result, type_).unwrap();
    result.into_iter().next().unwrap()
}

fn write_schema_definition(schema: &SDLSchema, result: &mut Vec<String>) -> FmtResult {
    let mut printer = Printer::new(schema, result);
    printer.print_schema_definition()
}

fn write_directives(schema: &SDLSchema, result: &mut Vec<String>) -> FmtResult {
    let mut printer = Printer::new(schema, result);
    printer.print_directives()
}

fn write_directive(
    schema: &SDLSchema,
    result: &mut Vec<String>,
    directive: &Directive,
) -> FmtResult {
    let mut printer = Printer::new(schema, result);
    printer.print_directive(directive)
}

fn write_types(schema: &SDLSchema, result: &mut Vec<String>) -> FmtResult {
    let mut printer = Printer::new(schema, result);
    printer.print_types()
}

fn write_types_as_shards<'a>(
    schema: &SDLSchema,
    shards: &'a mut Vec<String>,
    shard_count: usize,
    type_shards: &'a mut FnvHashMap<StringKey, Vec<String>>,
) -> FmtResult {
    let mut printer = Printer::new_with_shards(schema, shards, shard_count, Some(type_shards));
    printer.print_types()
}

fn write_type(schema: &SDLSchema, result: &mut Vec<String>, type_: Type) -> FmtResult {
    let mut printer = Printer::new(schema, result);
    printer.print_type(type_)
}

struct Printer<'schema, 'writer> {
    schema: &'schema SDLSchema,
    // Each writer here represents a shard
    writers: &'writer mut Vec<String>,
    // Index to specify which shard to write for a given type
    writers_index: usize,
    shard_count: usize,
    // Each writer here represents shards for individual types.
    // For e.g you may want to shard huge types like Query.
    type_writers: Option<&'writer mut FnvHashMap<StringKey, Vec<String>>>,
    // When sharding individual types, this represents index to
    // specify which shard to wite for a given type and field
    type_writers_index: Option<(StringKey, usize)>,
}

impl<'schema, 'writer, 'curent_writer> Printer<'schema, 'writer> {
    fn new(schema: &'schema SDLSchema, writers: &'writer mut Vec<String>) -> Self {
        Self {
            schema,
            writers_index: 0,
            writers,
            shard_count: DEAULT_SHARD_COUNT,
            type_writers: None,
            type_writers_index: None,
        }
    }

    fn new_with_shards(
        schema: &'schema SDLSchema,
        writers: &'writer mut Vec<String>,
        shard_count: usize,
        type_writers: Option<&'writer mut FnvHashMap<StringKey, Vec<String>>>,
    ) -> Self {
        Self {
            schema,
            writers_index: 0,
            writers,
            shard_count,
            type_writers,
            type_writers_index: None,
        }
    }

    fn print_schema_definition(&mut self) -> FmtResult {
        let query_type_name = self
            .schema
            .query_type()
            .map(|type_| self.schema.get_type_name(type_));

        let mutation_type_name = self
            .schema
            .mutation_type()
            .map(|type_| self.schema.get_type_name(type_));

        let subscription_type_name = self
            .schema
            .subscription_type()
            .map(|type_| self.schema.get_type_name(type_));

        write!(self.writer(), "schema {{")?;
        self.print_new_line()?;
        if let Some(type_name) = query_type_name {
            writeln!(self.writer(), "  query: {}", type_name)?;
        }
        if let Some(type_name) = mutation_type_name {
            writeln!(self.writer(), "  mutation: {}", type_name)?;
        }
        if let Some(type_name) = subscription_type_name {
            writeln!(self.writer(), "  subscription: {}", type_name)?;
        }
        write!(self.writer(), "}}")?;
        self.print_definition_end()
    }

    fn print_directives(&mut self) -> FmtResult {
        if self.schema.get_directives().count() == 0 {
            return Ok(());
        }
        let mut ordered_directives = self.schema.get_directives().collect::<Vec<_>>();
        ordered_directives.sort_by_key(|dir| dir.name);
        for directive in ordered_directives {
            self.print_directive(directive)?;
        }
        Ok(())
    }

    fn print_directive(&mut self, directive: &Directive) -> FmtResult {
        write!(self.writer(), "directive @{}", directive.name)?;
        self.print_args(&directive.arguments)?;
        write!(
            self.writer(),
            " on {}",
            directive.locations.iter().join(" | ")
        )?;
        self.print_definition_end()
    }

    fn print_types(&mut self) -> FmtResult {
        let ordered_type_map = self.schema.get_type_map().collect::<BTreeMap<_, _>>();
        for (_key, value) in ordered_type_map.iter() {
            self.print_type(**value)?;
        }
        Ok(())
    }

    fn print_type(&mut self, type_: Type) -> FmtResult {
        self.update_writer_index_for_type_start(&type_);
        match type_ {
            Type::Enum(id) => self.print_enum(id)?,
            Type::InputObject(id) => self.print_input_object(id)?,
            Type::Interface(id) => self.print_interface(id)?,
            Type::Object(id) => self.print_object(id)?,
            Type::Scalar(id) => self.print_scalar(id)?,
            Type::Union(id) => self.print_union(id)?,
        }
        self.update_writer_index_for_type_end();
        Ok(())
    }

    fn print_scalar(&mut self, id: ScalarID) -> FmtResult {
        let scalar = self.schema.scalar(id);
        write!(self.writer(), "scalar {}", scalar.name.item)?;
        self.print_directive_values(&scalar.directives)?;
        self.print_definition_end()
    }

    fn print_object(&mut self, id: ObjectID) -> FmtResult {
        let object = self.schema.object(id);
        write!(self.writer(), "type {}", object.name.item)?;
        self.print_implementing_interfaces(&object.interfaces)?;
        self.print_directive_values(&object.directives)?;
        self.print_space()?;
        self.print_fields(&object.fields, object.name.item)?;
        self.print_definition_end()
    }

    fn print_interface(&mut self, id: InterfaceID) -> FmtResult {
        let interface = self.schema.interface(id);
        write!(self.writer(), "interface {}", interface.name.item)?;
        self.print_implementing_interfaces(&interface.interfaces)?;
        self.print_directive_values(&interface.directives)?;
        self.print_space()?;
        self.print_fields(&interface.fields, interface.name.item)?;
        self.print_definition_end()
    }

    fn print_union(&mut self, id: UnionID) -> FmtResult {
        let union_ = self.schema.union(id);
        write!(self.writer(), "union {}", union_.name.item)?;
        self.print_directive_values(&union_.directives)?;
        if !union_.members.is_empty() {
            let union_members = union_
                .members
                .iter()
                .map(|id| self.schema.object(*id).name.item)
                .collect::<Vec<_>>()
                .iter()
                .join(" | ");
            write!(self.writer(), " = {}", union_members)?;
        }
        self.print_definition_end()
    }

    fn print_enum(&mut self, id: EnumID) -> FmtResult {
        let enum_ = self.schema.enum_(id);
        write!(self.writer(), "enum {}", enum_.name.item)?;
        self.print_directive_values(&enum_.directives)?;
        self.print_space()?;
        self.print_enum_values(&enum_.values)?;
        self.print_definition_end()
    }

    fn print_input_object(&mut self, id: InputObjectID) -> FmtResult {
        let input_object = self.schema.input_object(id);
        write!(self.writer(), "input {}", input_object.name.item)?;
        self.print_directive_values(&input_object.directives)?;
        self.print_space()?;
        self.print_input_object_fields(&input_object.fields)?;
        self.print_definition_end()
    }

    fn print_fields(&mut self, fields: &[FieldID], type_name: impl Lookup) -> FmtResult {
        if fields.is_empty() {
            return Ok(());
        }
        write!(self.writer(), "{{")?;
        self.print_new_line()?;
        let typename = type_name.lookup().intern();
        for field_id in fields {
            let field = &self.schema.field(*field_id);
            self.update_writer_index_for_field_start(field.name.item, typename);
            self.print_space()?;
            self.print_space()?;
            write!(self.writer(), "{}", field.name.item)?;
            self.print_args(&field.arguments)?;
            let type_string = self.schema.get_type_string(&field.type_);
            write!(self.writer(), ": {}", type_string)?;
            self.print_directive_values(&field.directives)?;
            self.print_new_line()?;
        }
        self.update_writer_index_for_all_field_end(typename);
        write!(self.writer(), "}}")
    }

    fn print_args(&mut self, args: &ArgumentDefinitions) -> FmtResult {
        if args.is_empty() {
            return Ok(());
        }
        let mut first = true;
        write!(self.writer(), "(")?;
        for arg in args.iter() {
            if first {
                first = false;
            } else {
                write!(self.writer(), ", ")?;
            }
            let type_string = self.schema.get_type_string(&arg.type_);
            write!(self.writer(), "{}: {}", arg.name, type_string,)?;
            if let Some(default) = &arg.default_value {
                write!(self.writer(), " = {}", default,)?;
            }
        }
        write!(self.writer(), ")")
    }

    fn print_enum_values(&mut self, values: &[EnumValue]) -> FmtResult {
        if values.is_empty() {
            return Ok(());
        }
        write!(self.writer(), "{{")?;
        self.print_new_line()?;
        for value in values {
            write!(self.writer(), "  {}", value.value,)?;
            self.print_directive_values(&value.directives)?;
            self.print_new_line()?;
        }
        write!(self.writer(), "}}")
    }

    fn print_input_object_fields(&mut self, args: &ArgumentDefinitions) -> FmtResult {
        if args.is_empty() {
            return Ok(());
        }
        write!(self.writer(), "{{")?;
        self.print_new_line()?;
        for arg in args.iter() {
            let type_string = self.schema.get_type_string(&arg.type_);
            write!(self.writer(), "  {}: {}", arg.name, type_string,)?;
            if let Some(default) = &arg.default_value {
                write!(self.writer(), " = {}", default,)?;
            }
            self.print_new_line()?;
        }
        write!(self.writer(), "}}")
    }

    fn print_directive_values(&mut self, directives: &[DirectiveValue]) -> FmtResult {
        if directives.is_empty() {
            return Ok(());
        }
        for directive in directives {
            write!(self.writer(), " @{}", directive.name)?;
            self.print_directive_argument_values(&directive.arguments)?;
        }
        Ok(())
    }

    fn print_directive_argument_values(&mut self, values: &[ArgumentValue]) -> FmtResult {
        if values.is_empty() {
            return Ok(());
        }
        let mut first = true;
        write!(self.writer(), "(")?;
        for value in values.iter() {
            if first {
                first = false;
            } else {
                write!(self.writer(), ", ")?;
            }
            write!(self.writer(), "{}: {}", value.name, value.value)?;
        }
        write!(self.writer(), ")")
    }

    fn print_implementing_interfaces(&mut self, interfaces: &[InterfaceID]) -> FmtResult {
        if !interfaces.is_empty() {
            let interface_names = interfaces
                .iter()
                .map(|id| self.schema.interface(*id).name.item)
                .join(" & ");
            write!(self.writer(), " implements {}", interface_names,)?;
        }
        Ok(())
    }

    fn print_definition_end(&mut self) -> FmtResult {
        self.print_new_line()?;
        self.print_new_line()
    }

    fn print_new_line(&mut self) -> FmtResult {
        writeln!(self.writer())
    }

    fn print_space(&mut self) -> FmtResult {
        write!(self.writer(), " ")
    }

    fn writer(&mut self) -> &mut String {
        if let Some(type_writers) = &mut self.type_writers {
            if let Some((type_name, index)) = self.type_writers_index {
                if let Some(writers) = type_writers.get_mut(&type_name) {
                    return writers.get_mut(index).unwrap();
                }
            }
        }
        self.writers.get_mut(self.writers_index).unwrap()
    }

    fn update_writer_index_for_type_start(&mut self, type_: &Type) {
        if self.shard_count > 1 {
            let type_name = self.schema.get_type_name(*type_);
            let type_hash = calculate_hash(&type_name.lookup()) as usize;
            self.writers_index = type_hash % self.shard_count;
            if let Some(type_writers) = &self.type_writers {
                if type_writers.contains_key(&type_name) {
                    self.type_writers_index = Some((type_name, 0));
                }
            }
        }
    }

    fn update_writer_index_for_type_end(&mut self) {
        self.type_writers_index = None;
    }

    fn update_writer_index_for_field_start(&mut self, field_name: StringKey, type_name: StringKey) {
        if let Some(type_writers) = &mut self.type_writers {
            if let Some(writers) = type_writers.get_mut(&type_name) {
                let index = (calculate_hash(&field_name.lookup()) as usize) % writers.len();
                self.type_writers_index = Some((type_name, index));
            }
        }
    }

    fn update_writer_index_for_all_field_end(&mut self, type_name: StringKey) {
        if let Some(type_writers) = &self.type_writers {
            if let Some(writers) = type_writers.get(&type_name) {
                self.type_writers_index = Some((type_name, writers.len() - 1));
            }
        }
    }
}

fn is_schema_of_common_name(schema: &SDLSchema) -> bool {
    schema.query_type().is_some()
        && schema.mutation_type().is_some()
        && schema.subscription_type().is_some()
}

fn has_schema_definition_types(schema: &SDLSchema) -> bool {
    schema.query_type().is_some()
        || schema.mutation_type().is_some()
        || schema.subscription_type().is_some()
}

fn calculate_hash<T: Hash>(t: &T) -> u64 {
    let mut s = DefaultHasher::new();
    t.hash(&mut s);
    s.finish()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<08e2c143cd60aa5cd2bfbb8b67388e8b>>
 */

mod print_schema;

use print_schema::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn kitchen_sink() {
    let input = include_str!("print_schema/fixtures/kitchen-sink.graphql");
    let expected = include_str!("print_schema/fixtures/kitchen-sink.expected");
    test_fixture(transform_fixture, "kitchen-sink.graphql", "print_schema/fixtures/kitchen-sink.expected", input, expected);
}

#[test]
fn no_schema() {
    let input = include_str!("print_schema/fixtures/no-schema.graphql");
    let expected = include_str!("print_schema/fixtures/no-schema.expected");
    test_fixture(transform_fixture, "no-schema.graphql", "print_schema/fixtures/no-schema.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use fixture_tests::Fixture;
use intern::string_key::Intern;
use schema::build_schema;
use schema_print::print_directives;
use schema_print::print_types_directives_as_shards;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let schema = build_schema(fixture.content).unwrap();
    let mut type_shard_count = fnv::FnvHashMap::default();
    type_shard_count.insert("Query".intern(), 2);
    let directives = print_directives(&schema);
    let types = print_types_directives_as_shards(&schema, 4, type_shard_count)
        .join("\n=======Shard=======\n\n");
    Ok(format!("{}{}", directives, types))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::ArgumentName;
use common::InterfaceName;
use common::ObjectName;
use intern::string_key::StringKey;
use schema::Type;
use schema::TypeReference;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum SchemaValidationError {
    #[error("'{0}' root type must be provided.")]
    MissingRootType(StringKey),

    #[error("'{0}' root type must be Object type. Found '{1:?}'")]
    InvalidRootType(StringKey, Type),

    #[error("Name '{0}' must not begin with '__', which is reserved by GraphQL introspection.")]
    InvalidNamePrefix(String),

    #[error("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but '{0}' does not.")]
    InvalidName(String),

    #[error("Duplicate field '{0}' found.")]
    DuplicateField(StringKey),

    #[error("Duplicate argument '{0}' found on field/directive '{1}'.")]
    DuplicateArgument(ArgumentName, StringKey),

    #[error("Type must define one or more fields.")]
    TypeWithNoFields,

    #[error("The type of '{0}.{1}' must be Output Type but got: '{2:?}'.")]
    InvalidFieldType(StringKey, StringKey, TypeReference<Type>),

    #[error("The type of '{0}.{1}({2}:)' must be InputType but got: '{3:?}'.")]
    InvalidArgumentType(StringKey, StringKey, ArgumentName, TypeReference<Type>),

    #[error("Type '{0}' can only implement '{1}' once.")]
    DuplicateInterfaceImplementation(StringKey, InterfaceName),

    #[error("Interface field '{0}.{1}' expected but '{2}' does not provide it.")]
    InterfaceFieldNotProvided(InterfaceName, StringKey, StringKey),

    #[error("Interface field '{0}.{1}' expects type '{2}' but '{3}.{1}' is of type '{4}'.")]
    NotASubType(InterfaceName, StringKey, StringKey, StringKey, StringKey),

    #[error(
        "Interface field argument '{0}.{1}({2}:)' expected but '{3}.{1}' does not provide it."
    )]
    InterfaceFieldArgumentNotProvided(InterfaceName, StringKey, ArgumentName, StringKey),

    #[error(
        "Interface field argument '{0}.{1}({2}:)' expects type '{3}' but '{4}.{1}({2}:)' is type '{5}'."
    )]
    NotEqualType(
        InterfaceName,
        StringKey,
        ArgumentName,
        StringKey,
        StringKey,
        StringKey,
    ),

    #[error(
        "Object field '{0}.{1}' includes required argument '{2}' that is missing from the Interface field '{3}.{1}'."
    )]
    MissingRequiredArgument(StringKey, StringKey, ArgumentName, InterfaceName),

    #[error("Union type must define one or more member types.")]
    UnionWithNoMembers(StringKey),

    #[error("Union can only include member {0} once.")]
    DuplicateMember(ObjectName),

    #[error("Enum must define one or more values.")]
    EnumWithNoValues,

    #[error("Enum cannot include value: {0}.")]
    InvalidEnumValue(StringKey),

    #[error("Cyclic reference found for interface inheritance: {0}.")]
    CyclicInterfaceInheritance(String),
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod errors;

use std::fmt::Write;
use std::sync::Mutex;
use std::time::Instant;

use common::DirectiveName;
use common::InterfaceName;
use common::Named;
use errors::*;
use fnv::FnvHashMap;
use fnv::FnvHashSet;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use lazy_static::lazy_static;
use rayon::prelude::*;
use regex::Regex;
use schema::EnumID;
use schema::Field;
use schema::FieldID;
use schema::InputObjectID;
use schema::Interface;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;
use schema::TypeReference;
use schema::TypeWithFields;
use schema::UnionID;
use schema_print::print_directive;
use schema_print::print_type;

lazy_static! {
    static ref INTROSPECTION_TYPES: FnvHashSet<StringKey> = vec![
        "__Schema".intern(),
        "__Directive".intern(),
        "__DirectiveLocation".intern(),
        "__Type".intern(),
        "__Field".intern(),
        "__InputValue".intern(),
        "__EnumValue".intern(),
        "__TypeKind".intern(),
    ]
    .into_iter()
    .collect();
    static ref QUERY: StringKey = "Query".intern();
    static ref SUBSCRIPTION: StringKey = "Subscription".intern();
    static ref MUTATION: StringKey = "Mutation".intern();
    static ref TYPE_NAME_REGEX: Regex = Regex::new(r"^[_a-zA-Z][_a-zA-Z0-9]*$").unwrap();
}

pub fn validate(schema: &SDLSchema) -> ValidationContext<'_> {
    let mut validation_context = ValidationContext::new(schema);
    validation_context.validate();
    validation_context
}

#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ValidationContextType {
    TypeNode(StringKey),
    DirectiveNode(StringKey),
    None,
}

impl ValidationContextType {
    pub fn type_name(self) -> String {
        match self {
            ValidationContextType::DirectiveNode(type_name)
            | ValidationContextType::TypeNode(type_name) => type_name.lookup().to_string(),
            _ => "None".to_string(),
        }
    }
}

pub struct ValidationContext<'schema> {
    pub schema: &'schema SDLSchema,
    pub errors: Mutex<FnvHashMap<ValidationContextType, Vec<SchemaValidationError>>>,
}

impl<'schema> ValidationContext<'schema> {
    pub fn new(schema: &'schema SDLSchema) -> Self {
        Self {
            schema,
            errors: Mutex::new(FnvHashMap::default()),
        }
    }

    fn validate(&mut self) {
        let now = Instant::now();
        self.validate_root_types();
        self.validate_directives();
        self.validate_types();
        println!("Validated Schema in {}ms", now.elapsed().as_millis());
        println!(
            "Found {} validation errors",
            self.errors.lock().unwrap().len()
        )
    }

    fn validate_root_types(&mut self) {
        self.validate_root_type(self.schema.query_type(), *QUERY);
        self.validate_root_type(self.schema.subscription_type(), *SUBSCRIPTION);
        self.validate_root_type(self.schema.mutation_type(), *MUTATION);
    }

    fn validate_root_type(&self, root_type: Option<Type>, type_name: StringKey) {
        if let Some(type_) = root_type {
            if !type_.is_object() {
                self.report_error(
                    SchemaValidationError::InvalidRootType(type_name, type_),
                    ValidationContextType::TypeNode(type_name),
                );
            }
        } else if type_name == *QUERY {
            self.add_error(SchemaValidationError::MissingRootType(type_name));
        }
    }

    fn validate_directives(&mut self) {
        for directive in self.schema.get_directives() {
            let context = ValidationContextType::DirectiveNode(directive.name.0);
            self.validate_name(directive.name.0, context);
            let mut arg_names = FnvHashSet::default();
            for argument in directive.arguments.iter() {
                self.validate_name(argument.name.0, context);

                // Ensure unique arguments per directive.
                if arg_names.contains(&argument.name) {
                    self.report_error(
                        SchemaValidationError::DuplicateArgument(argument.name, directive.name.0),
                        context,
                    );
                    continue;
                }
                arg_names.insert(argument.name);
            }
        }
    }

    fn validate_types(&mut self) {
        let types = self.schema.get_type_map().collect::<Vec<_>>();
        types.par_iter().for_each(|(type_name, type_)| {
            // Ensure it is named correctly (excluding introspection types).
            if !is_introspection_type(type_, **type_name) {
                self.validate_name(**type_name, ValidationContextType::TypeNode(**type_name));
            }
            match type_ {
                Type::Enum(id) => {
                    // Ensure Enums have valid values.
                    self.validate_enum_type(*id);
                }
                Type::InputObject(id) => {
                    // Ensure Input Object fields are valid.
                    self.validate_input_object_fields(*id);
                }
                Type::Interface(id) => {
                    let interface = self.schema.interface(*id);
                    // Ensure fields are valid
                    self.validate_fields(**type_name, &interface.fields);

                    // Validate cyclic references
                    if !self.validate_cyclic_implements_reference(interface) {
                        // Ensure interface implement the interfaces they claim to.
                        self.validate_type_with_interfaces(interface);
                    }
                }
                Type::Object(id) => {
                    let object = self.schema.object(*id);
                    // Ensure fields are valid
                    self.validate_fields(**type_name, &object.fields);

                    // Ensure objects implement the interfaces they claim to.
                    self.validate_type_with_interfaces(object);
                }
                Type::Union(id) => {
                    // Ensure Unions include valid member types.
                    self.validate_union_members(*id);
                }
                Type::Scalar(_id) => {}
            };
        });
    }

    fn validate_fields(&self, type_name: StringKey, fields: &[FieldID]) {
        let context = ValidationContextType::TypeNode(type_name);
        // Must define one or more fields.
        if fields.is_empty() {
            self.report_error(SchemaValidationError::TypeWithNoFields, context)
        }

        let mut field_names = FnvHashSet::default();
        for field_id in fields {
            let field = self.schema.field(*field_id);
            if field_names.contains(&field.name.item) {
                self.report_error(
                    SchemaValidationError::DuplicateField(field.name.item),
                    context,
                );
                continue;
            }
            field_names.insert(field.name.item);

            // Ensure they are named correctly.
            self.validate_name(field.name.item, context);

            // Ensure the type is an output type
            if !is_output_type(&field.type_) {
                self.report_error(
                    SchemaValidationError::InvalidFieldType(
                        type_name,
                        field.name.item,
                        field.type_.clone(),
                    ),
                    context,
                )
            }

            let mut arg_names = FnvHashSet::default();
            for argument in field.arguments.iter() {
                // Ensure they are named correctly.
                self.validate_name(argument.name.0, context);

                // Ensure they are unique per field.
                // Ensure unique arguments per directive.
                if arg_names.contains(&argument.name) {
                    self.report_error(
                        SchemaValidationError::DuplicateArgument(argument.name, field.name.item),
                        context,
                    );
                    continue;
                }
                arg_names.insert(argument.name);

                // Ensure the type is an input type
                if !is_input_type(&argument.type_) {
                    self.report_error(
                        SchemaValidationError::InvalidArgumentType(
                            type_name,
                            field.name.item,
                            argument.name,
                            argument.type_.clone(),
                        ),
                        context,
                    );
                }
            }
        }
    }

    fn validate_union_members(&self, id: UnionID) {
        let union = self.schema.union(id);
        let context = ValidationContextType::TypeNode(union.name.item);
        if union.members.is_empty() {
            self.report_error(
                SchemaValidationError::UnionWithNoMembers(union.name.item),
                context,
            );
        }

        let mut member_names = FnvHashSet::default();
        for member in union.members.iter() {
            let member_name = self.schema.object(*member).name.item;
            if member_names.contains(&member_name) {
                self.report_error(SchemaValidationError::DuplicateMember(member_name), context);
                continue;
            }
            member_names.insert(member_name);
        }
    }

    fn validate_enum_type(&self, id: EnumID) {
        let enum_ = self.schema.enum_(id);
        let context = ValidationContextType::TypeNode(enum_.name.item.0);
        if enum_.values.is_empty() {
            self.report_error(SchemaValidationError::EnumWithNoValues, context);
        }

        for value in enum_.values.iter() {
            // Ensure valid name.
            self.validate_name(value.value, context);
            let value_name = value.value.lookup();
            if value_name == "true" || value_name == "false" || value_name == "null" {
                self.report_error(
                    SchemaValidationError::InvalidEnumValue(value.value),
                    context,
                );
            }
        }
    }

    fn validate_input_object_fields(&self, id: InputObjectID) {
        let input_object = self.schema.input_object(id);
        let context = ValidationContextType::TypeNode(input_object.name.item.0);
        if input_object.fields.is_empty() {
            self.report_error(SchemaValidationError::TypeWithNoFields, context);
        }

        // Ensure the arguments are valid
        for field in input_object.fields.iter() {
            // Ensure they are named correctly.
            self.validate_name(field.name.0, context);

            // Ensure the type is an input type
            if !is_input_type(&field.type_) {
                self.report_error(
                    SchemaValidationError::InvalidArgumentType(
                        input_object.name.item.0,
                        field.name.0,
                        field.name,
                        field.type_.clone(),
                    ),
                    context,
                );
            }
        }
    }

    fn validate_type_with_interfaces<T: TypeWithFields + Named>(&self, type_: &T) {
        let typename = type_.name().lookup().intern();
        let mut interface_names = FnvHashSet::default();
        for interface_id in type_.interfaces().iter() {
            let interface = self.schema.interface(*interface_id);
            if interface_names.contains(&interface.name) {
                self.report_error(
                    SchemaValidationError::DuplicateInterfaceImplementation(
                        typename,
                        interface.name.item,
                    ),
                    ValidationContextType::TypeNode(typename),
                );
                continue;
            }
            interface_names.insert(interface.name);
            self.validate_type_implements_interface(type_, interface);
        }
    }

    fn validate_type_implements_interface<T: TypeWithFields + Named>(
        &self,
        type_: &T,
        interface: &Interface,
    ) {
        let typename = type_.name().lookup().intern();
        let object_field_map = self.field_map(type_.fields());
        let interface_field_map = self.field_map(&interface.fields);
        let context = ValidationContextType::TypeNode(typename);

        // Assert each interface field is implemented.
        for (field_name, interface_field) in interface_field_map {
            // Assert interface field exists on object.
            if !object_field_map.contains_key(&field_name) {
                self.report_error(
                    SchemaValidationError::InterfaceFieldNotProvided(
                        interface.name.item,
                        field_name,
                        typename,
                    ),
                    context,
                );
                continue;
            }

            let object_field = object_field_map.get(&field_name).unwrap();
            // Assert interface field type is satisfied by object field type, by being
            // a valid subtype. (covariant)
            if !self
                .schema
                .is_type_subtype_of(&object_field.type_, &interface_field.type_)
            {
                self.report_error(
                    SchemaValidationError::NotASubType(
                        interface.name.item,
                        field_name,
                        self.schema.get_type_name(interface_field.type_.inner()),
                        typename,
                        self.schema.get_type_name(object_field.type_.inner()),
                    ),
                    context,
                );
            }

            // Assert each interface field arg is implemented.
            for interface_argument in interface_field.arguments.iter() {
                let object_argument = object_field
                    .arguments
                    .iter()
                    .find(|arg| arg.name == interface_argument.name);

                // Assert interface field arg exists on object field.
                if object_argument.is_none() {
                    self.report_error(
                        SchemaValidationError::InterfaceFieldArgumentNotProvided(
                            interface.name.item,
                            field_name,
                            interface_argument.name,
                            typename,
                        ),
                        context,
                    );
                    continue;
                }
                let object_argument = object_argument.unwrap();

                // Assert interface field arg type matches object field arg type.
                // (invariant)
                // TODO: change to contravariant?
                if interface_argument.type_ != object_argument.type_ {
                    self.report_error(
                        SchemaValidationError::NotEqualType(
                            interface.name.item,
                            field_name,
                            interface_argument.name,
                            self.schema.get_type_name(interface_argument.type_.inner()),
                            typename,
                            self.schema.get_type_name(object_argument.type_.inner()),
                        ),
                        context,
                    );
                }
                // TODO: validate default values?
            }

            // Assert additional arguments must not be required.
            for object_argument in object_field.arguments.iter() {
                if !interface_field.arguments.contains(object_argument.name.0)
                    && object_argument.type_.is_non_null()
                {
                    self.report_error(
                        SchemaValidationError::MissingRequiredArgument(
                            typename,
                            field_name,
                            object_argument.name,
                            interface.name.item,
                        ),
                        context,
                    );
                }
            }
        }
    }

    fn validate_cyclic_implements_reference(&self, interface: &Interface) -> bool {
        for id in interface.interfaces() {
            let mut path = Vec::new();
            let mut visited = FnvHashSet::default();
            if self.has_path(
                self.schema.interface(*id),
                interface.name.item,
                &mut path,
                &mut visited,
            ) {
                self.report_error(
                    SchemaValidationError::CyclicInterfaceInheritance(format!(
                        "{}->{}",
                        path.iter()
                            .map(|name| name.lookup())
                            .collect::<Vec<_>>()
                            .join("->"),
                        interface.name.item
                    )),
                    ValidationContextType::TypeNode(interface.name.item.0),
                );
                return true;
            }
        }
        false
    }

    fn has_path(
        &self,
        root: &Interface,
        target: InterfaceName,
        path: &mut Vec<StringKey>,
        visited: &mut FnvHashSet<StringKey>,
    ) -> bool {
        if visited.contains(&root.name.item.0) {
            return false;
        }

        if root.name.item == target {
            return true;
        }

        path.push(root.name.item.0);
        visited.insert(root.name.item.0);
        for id in root.interfaces() {
            if self.has_path(self.schema.interface(*id), target, path, visited) {
                return true;
            }
        }
        path.remove(path.len() - 1);
        false
    }

    fn validate_name(&self, name: StringKey, context: ValidationContextType) {
        let name = name.lookup();
        let mut chars = name.chars();
        if name.len() > 1 && chars.next() == Some('_') && chars.next() == Some('_') {
            self.report_error(
                SchemaValidationError::InvalidNamePrefix(name.to_string()),
                context,
            );
        }
        if !TYPE_NAME_REGEX.is_match(name) {
            self.report_error(
                SchemaValidationError::InvalidName(name.to_string()),
                context,
            );
        }
    }

    fn field_map(&self, fields: &[FieldID]) -> FnvHashMap<StringKey, Field> {
        fields
            .iter()
            .map(|id| self.schema.field(*id))
            .map(|field| (field.name.item, field.clone()))
            .collect::<FnvHashMap<_, _>>()
    }

    fn report_error(&self, error: SchemaValidationError, context: ValidationContextType) {
        self.errors
            .lock()
            .unwrap()
            .entry(context)
            .or_insert_with(Vec::new)
            .push(error);
    }

    fn add_error(&self, error: SchemaValidationError) {
        self.report_error(error, ValidationContextType::None);
    }

    pub fn print_errors(&self) -> String {
        let mut builder: String = String::new();
        let errors = self.errors.lock().unwrap();
        let mut contexts: Vec<_> = errors.keys().collect();
        contexts.sort_by_key(|context| context.type_name());
        for context in contexts {
            match context {
                ValidationContextType::None => writeln!(builder, "Errors:").unwrap(),
                ValidationContextType::TypeNode(type_name) => writeln!(
                    builder,
                    "Type {} with definition:\n\t{}\nhad errors:",
                    type_name,
                    print_type(self.schema, self.schema.get_type(*type_name).unwrap()).trim_end()
                )
                .unwrap(),
                ValidationContextType::DirectiveNode(directive_name) => writeln!(
                    builder,
                    "Directive {} with definition:\n\t{}\nhad errors:",
                    directive_name,
                    print_directive(
                        self.schema,
                        self.schema
                            .get_directive(DirectiveName(*directive_name))
                            .unwrap()
                    )
                    .trim_end()
                )
                .unwrap(),
            }
            let mut error_strings = errors
                .get(context)
                .unwrap()
                .iter()
                .map(|error| format!("\t* {}", error))
                .collect::<Vec<_>>();
            error_strings.sort();
            writeln!(builder, "{}", error_strings.join("\n")).unwrap();
            writeln!(builder).unwrap();
        }
        builder
    }
}

fn is_named_type(type_: &Type) -> bool {
    type_.is_enum()
        || type_.is_input_type()
        || type_.is_interface()
        || type_.is_object()
        || type_.is_scalar()
        || type_.is_union()
}

fn is_introspection_type(type_: &Type, type_name: StringKey) -> bool {
    is_named_type(type_) && INTROSPECTION_TYPES.contains(&type_name)
}

fn is_output_type(type_: &TypeReference<Type>) -> bool {
    let type_ = type_.inner();
    type_.is_enum()
        || type_.is_interface()
        || type_.is_object()
        || type_.is_scalar()
        || type_.is_union()
}

fn is_input_type(type_: &TypeReference<Type>) -> bool {
    let type_ = type_.inner();
    type_.is_enum() || type_.is_input_type() || type_.is_scalar()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fs;
use std::path::Path;

use clap::Parser;
use common::DiagnosticsResult;
use schema::build_schema;
use schema::SDLSchema;
use schema_validate_lib::validate;

#[derive(Parser)]
#[clap(name = "schema-validate", about = "Binary to Validate GraphQL Schema.")]
struct Opt {
    /// Path to Schema SDL. If schema is sharded, this is directory.
    #[clap(long)]
    schema_path: String,
}

pub fn main() {
    let opt = Opt::parse();
    match build_schema_from_file(&opt.schema_path) {
        Ok(schema) => {
            let validation_context = validate(&schema);
            if !validation_context.errors.lock().unwrap().is_empty() {
                eprintln!(
                    "Schema failed validation with below errors:\n{}",
                    validation_context.print_errors()
                );
                std::process::exit(1);
            }
        }
        Err(error) => {
            eprintln!("Failed to parse schema:\n{:?}", error);
            std::process::exit(1);
        }
    }
}

fn build_schema_from_file(schema_file: &str) -> DiagnosticsResult<SDLSchema> {
    let path = Path::new(schema_file);
    let data = if path.is_file() {
        fs::read_to_string(path).unwrap()
    } else {
        let mut buffer = String::new();
        for entry in path.read_dir().unwrap() {
            buffer.push_str(&fs::read_to_string(entry.unwrap().path()).unwrap());
        }
        buffer
    };
    build_schema(&data)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<e0f69125973077ab82c37b910a577a54>>
 */

mod validate_schema;

use validate_schema::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn validate_directives() {
    let input = include_str!("validate_schema/fixtures/validate_directives.graphql");
    let expected = include_str!("validate_schema/fixtures/validate_directives.expected");
    test_fixture(transform_fixture, "validate_directives.graphql", "validate_schema/fixtures/validate_directives.expected", input, expected);
}

#[test]
fn validate_enum() {
    let input = include_str!("validate_schema/fixtures/validate_enum.graphql");
    let expected = include_str!("validate_schema/fixtures/validate_enum.expected");
    test_fixture(transform_fixture, "validate_enum.graphql", "validate_schema/fixtures/validate_enum.expected", input, expected);
}

#[test]
fn validate_interface_implements_interface_cyclic() {
    let input = include_str!("validate_schema/fixtures/validate_interface_implements_interface_cyclic.graphql");
    let expected = include_str!("validate_schema/fixtures/validate_interface_implements_interface_cyclic.expected");
    test_fixture(transform_fixture, "validate_interface_implements_interface_cyclic.graphql", "validate_schema/fixtures/validate_interface_implements_interface_cyclic.expected", input, expected);
}

#[test]
fn validate_object() {
    let input = include_str!("validate_schema/fixtures/validate_object.graphql");
    let expected = include_str!("validate_schema/fixtures/validate_object.expected");
    test_fixture(transform_fixture, "validate_object.graphql", "validate_schema/fixtures/validate_object.expected", input, expected);
}

#[test]
fn validate_root_types() {
    let input = include_str!("validate_schema/fixtures/validate_root_types.graphql");
    let expected = include_str!("validate_schema/fixtures/validate_root_types.expected");
    test_fixture(transform_fixture, "validate_root_types.graphql", "validate_schema/fixtures/validate_root_types.expected", input, expected);
}

#[test]
fn validate_union() {
    let input = include_str!("validate_schema/fixtures/validate_union.graphql");
    let expected = include_str!("validate_schema/fixtures/validate_union.expected");
    test_fixture(transform_fixture, "validate_union.graphql", "validate_schema/fixtures/validate_union.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use fixture_tests::Fixture;
use schema::build_schema;
use schema_validate_lib::validate;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let schema = build_schema(fixture.content).unwrap();
    Ok(validate(&schema).print_errors())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

use lazy_static::lazy_static;
use md5::Digest;
use md5::Md5;
use regex::Regex;

lazy_static! {
    static ref RE: Regex = Regex::new("\x40generated (?:SignedSource<<([a-f0-9]{32})>>)").unwrap();
}

fn hash(data: &str) -> String {
    let mut md5 = Md5::new();
    md5.update(data);
    hex::encode(md5.finalize())
}
fn sign(data: &str) -> String {
    data.replace(NEWTOKEN, &format!("SignedSource<<{}>>", hash(data)))
}

pub const NEWTOKEN: &str = "<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie\x40IsG>>";
/// The signing token to be embedded in the file you wish to be signed.
pub const SIGNING_TOKEN: &str = "\x40generated <<SignedSource::*O*zOeWoEQle#+L!plEphiEmie\x40IsG>>";

/// Checks whether a file is signed *without* verifying the signature.
pub fn is_signed(data: &str) -> bool {
    RE.is_match(data)
}

/// Signs a source file which contains a signing token. Signing modifies only
/// the signing token, so the token should be placed inside a comment in order
/// for signing to not change code semantics.
pub fn sign_file(data: &str) -> String {
    try_sign_file(data).unwrap_or_else(|| {
        panic!(
            "sign_file(...): Cannot sign file without token {}",
            NEWTOKEN
        )
    })
}

pub fn try_sign_file(data: &str) -> Option<String> {
    if data.contains(NEWTOKEN) {
        Some(sign(data))
    } else {
        None
    }
}

/// Verifies the signature in a signed file.
pub fn is_valid_signature(data: &str) -> bool {
    if let Some(mat) = RE.find(data) {
        let actual = &data[mat.start() + 25..mat.end() - 2];
        let unsigned = RE.replace(data, SIGNING_TOKEN);
        return hash(&unsigned) == actual;
    }
    false
}

#[cfg(test)]
mod tests;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use super::*;

#[test]
fn test_sign_file() {
    let new_signing_result_1 = sign_file(&format!("# {}\ntest 1", SIGNING_TOKEN));
    assert_eq!(
        new_signing_result_1,
        "# \x40generated SignedSource<<d9b7b52f54978f54b84a0fd48145e470>>\ntest 1"
    );

    let new_signing_result_2 = sign_file(&format!("# {}\ntest 2", SIGNING_TOKEN));
    assert_eq!(
        new_signing_result_2,
        "# \x40generated SignedSource<<4c0c1ae4f5863c72731b2f543e830fd5>>\ntest 2"
    );
}

#[test]
fn test_sign_file_preexisting_token() {
    assert_eq!(
        try_sign_file(
            "# \x40generated SignedSource<<eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee>>\nalready signed test",
        ),
        None
    );
}

#[test]
fn test_sign_file_without_token() {
    assert_eq!(try_sign_file("# \x40generated no-token\nnot signed"), None);
}

#[test]
fn test_is_signed() {
    assert!(!is_signed("unsigned"));
    assert!(!is_signed("\x40generated unsigned"));
    assert!(!is_signed(SIGNING_TOKEN));
    assert!(is_signed(
        "\x40generated SignedSource<<4c0c1ae4f5863c72731b2f543e830fd5>>"
    ));
    let mut signed_string = sign_file(&format!("# {}\ntest", SIGNING_TOKEN));
    assert!(is_signed(&signed_string));
    signed_string.push_str("modified");
    assert!(is_signed(&signed_string));
}

#[test]
fn test_is_valid_signature() {
    let mut signed_string = sign_file(&format!("# {}\ntest", SIGNING_TOKEN));
    assert!(is_valid_signature(&signed_string));
    signed_string.push_str("modified");
    assert!(!is_valid_signature(&signed_string));
    assert!(!is_valid_signature("unsinged"));
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

fn main() {
    tauri_build::build()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use std::process::Command;

use serde::Deserialize;
use tauri::api::dialog::MessageDialogBuilder;
use tauri::api::dialog::MessageDialogKind;

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct ISLOutput {
    url: String,
    port: u32,
    pid: u32,
}

fn main() {
    open_isl_window();
}

fn get_spawn_isl_args() -> Vec<String> {
    let mut cmds: Vec<String> = std::env::args().skip(1).collect();

    // no args provided -> default to "sl web"
    if cmds.len() == 0 {
        cmds.insert(0, "sl".to_string());
        cmds.insert(1, "web".to_string());
    }

    cmds
}

fn spawn_isl_server() -> Result<ISLOutput, String> {
    let args = get_spawn_isl_args();

    println!("Spawning ISL server with: `{}`", args.join(" "));

    let result = Command::new(args.first().clone().unwrap())
        .args(args.iter().skip(1).collect::<Vec<&String>>())
        .arg("--json")
        .arg("--no-open")
        .arg("--platform")
        .arg("standalone")
        .output()
        .map_err(|err| err.to_string())?;

    let isl_output = String::from_utf8_lossy(&result.stdout);
    let json: ISLOutput = serde_json::from_str(&isl_output).map_err(|err| {
        format!(
            "Could not parse JSON: {}\nstdout: {}\nstderr: {}",
            err.to_string(),
            isl_output,
            String::from_utf8_lossy(&result.stderr)
        )
    })?;

    println!("output from ISL: {:?}", json);
    Ok(json)
}

fn open_isl_window() {
    tauri::Builder::default()
        .setup(move |app| {
            let json = spawn_isl_server();

            match json {
                Ok(json) => {
                    tauri::WindowBuilder::new(
                        app,
                        "external",
                        tauri::WindowUrl::External(json.url.parse().unwrap()),
                    )
                    .title("Interactive Smartlog")
                    .inner_size(1200.0, 1000.0)
                    .build()?;
                }
                Err(err) => {
                    show_error_dialog(app.handle(), "Error starting ISL".into(), err);
                }
            }
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

fn show_error_dialog(handle: tauri::AppHandle, title: String, err: String) {
    println!("{}", err);
    std::thread::spawn(move || {
        // the dialog requires a window to show successfully,
        // so we make a hidden blank window
        _ = tauri::WindowBuilder::new(&handle, "Error", tauri::WindowUrl::App("index.html".into()))
            .visible(false)
            .build()
            .unwrap();

        MessageDialogBuilder::new(title, err.to_string())
            .kind(MessageDialogKind::Error)
            .show(|_| {
                std::process::exit(1);
            });
    });
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use lazy_static::lazy_static;
#[cfg(any(fbcode_build, feature = "fb"))]
mod facebook;
#[cfg(any(fbcode_build, feature = "fb"))]
pub use facebook::get_env;
#[cfg(any(fbcode_build, feature = "fb"))]
pub use facebook::is_corp;
#[cfg(any(fbcode_build, feature = "fb"))]
pub use facebook::is_lab;
#[cfg(any(fbcode_build, feature = "fb"))]
pub use facebook::is_prod;

lazy_static! {
    pub static ref IN_PROD: bool = is_prod();
    pub static ref IN_CORP: bool = is_corp();
    pub static ref IN_LAB: bool = is_lab();
}

#[cfg(not(any(fbcode_build, feature = "fb")))]
pub fn get_env() -> u8 {
    0
}

#[cfg(not(any(fbcode_build, feature = "fb")))]
pub fn is_prod() -> bool {
    false
}

#[cfg(not(any(fbcode_build, feature = "fb")))]
pub fn is_corp() -> bool {
    false
}

#[cfg(not(any(fbcode_build, feature = "fb")))]
pub fn is_lab() -> bool {
    false
}

#[no_mangle]
pub extern "C" fn fb_get_env() -> u8 {
    get_env() as u8
}

#[no_mangle]
pub extern "C" fn fb_is_prod() -> bool {
    *IN_PROD
}

#[no_mangle]
pub extern "C" fn fb_is_corp() -> bool {
    *IN_CORP
}

#[no_mangle]
pub extern "C" fn fb_is_lab() -> bool {
    *IN_LAB
}

#[no_mangle]
pub extern "C" fn fb_has_servicerouter() -> bool {
    *IN_PROD
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

fn main() {
    println!(
        "This host is {}",
        if hostcaps::is_prod() {
            "prod"
        } else if hostcaps::is_corp() {
            "corp"
        } else {
            "lab"
        }
    );
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "hgclient crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=config__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "hgclient.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
::codegen_includer_proc_macro::include!();


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "lfs_server crate
ratelimits rate_limiting_config",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=lfs_server_config__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "lfs_server.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "megarepo_configs crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=megarepo_configs__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "megarepo_configs.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "megarepo_configs megarepo_configs
version_cconf_index crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=version_cconf_index__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "../version_cconf_index.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "config crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=replication_lag_config__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "config.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "observability_config crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=observability_config__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "observability_config.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "enable crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=pushredirect_enable__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "enable.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "qps_config crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=qps_config__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "qps_config.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "ratelimits crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=rate_limiting_config__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "ratelimits.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "redaction_set crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=redaction_set__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "redaction_set.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "commitsync crate
repos repos",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=commitsync__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "../commitsync.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "repos crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=repos__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "../repos.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "tunables crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=tunables_structs__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "tunables.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;
use thrift_compiler::GenContext;

#[rustfmt::skip]
fn main() {
    // Rerun if this gets rewritten.
    println!("cargo:rerun-if-changed=thrift_build.rs");

    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "xdb_gc crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env(GenContext::Lib).expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        // TODO: replace canonicalize() with std::path::absolute() when
        // https://github.com/rust-lang/rust/pull/91673 is available (~Rust 1.60)
        // and remove this block.
        #[cfg(windows)]
        let base_path = Path::new(
            base_path
                .as_path()
                .to_string_lossy()
                .trim_start_matches(r"\\?\"),
            )
            .to_path_buf();

        conf.base_path(base_path);

        let options = "types_crate=xdb_gc_structs__types,serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "xdb_gc.thrift"
        ])
        .expect("Failed while running thrift compilation");
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fs::File;
use std::fs::OpenOptions;
use std::io;
#[cfg(unix)]
use std::os::unix::fs::FileExt;
#[cfg(target_os = "linux")]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(target_vendor = "apple")]
use std::os::unix::io::AsRawFd;
#[cfg(windows)]
use std::os::windows::fs::FileExt;
#[cfg(windows)]
use std::os::windows::fs::OpenOptionsExt;
use std::path::Path;
use std::path::PathBuf;

use criterion::criterion_group;
use criterion::criterion_main;
use criterion::Bencher;
use criterion::Criterion;
use criterion::Throughput;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use rand::Rng;

const PAGE_SIZE: usize = 4096;
const DEFAULT_FILE_SIZE: u64 = 16 * 1024 * 1024;

mod pthread {
    use std::ffi::c_int;

    const PTHREAD_CANCEL_DISABLE: c_int = 0;
    const PTHREAD_CANCEL_ASYNCHRONOUS: c_int = 1;

    extern "C" {
        fn pthread_setcancelstate(__state: c_int, __oldstate: *mut c_int) -> c_int;
        fn pthread_setcanceltype(__type: c_int, __oldtype: *mut c_int) -> c_int;
    }

    pub fn without_pthread_cancellation<T, F: FnOnce() -> T>(func: F) -> T {
        let mut oldstate: c_int = 0;
        let mut oldtype: c_int = 0;
        unsafe {
            assert_eq!(
                0,
                pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &mut oldstate)
            );
            assert_eq!(
                0,
                pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &mut oldtype)
            );
        }

        let result = func();

        unsafe {
            assert_eq!(0, pthread_setcanceltype(oldtype, &mut oldtype));
            assert_eq!(0, pthread_setcancelstate(oldstate, &mut oldstate));
        }

        result
    }
}

trait PosIO {
    fn read_full_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()>;
    fn write_full_at(&self, buf: &[u8], offset: u64) -> io::Result<()>;
}

#[cfg(unix)]
impl PosIO for File {
    fn read_full_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> {
        self.read_exact_at(buf, offset)
    }

    fn write_full_at(&self, buf: &[u8], offset: u64) -> io::Result<()> {
        self.write_all_at(buf, offset)
    }
}

#[cfg(windows)]
impl PosIO for File {
    fn read_full_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
        while !buf.is_empty() {
            match self.seek_read(buf, offset) {
                Ok(0) => {
                    return Err(io::Error::new(
                        io::ErrorKind::UnexpectedEof,
                        "unexpected eof",
                    ));
                }
                Ok(n) => {
                    offset += n as u64;
                    buf = &mut buf[n..];
                }
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        Ok(())
    }

    fn write_full_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
        while !buf.is_empty() {
            match self.seek_write(buf, offset) {
                Ok(0) => {
                    return Err(io::Error::new(
                        io::ErrorKind::WriteZero,
                        "unexpected zero write",
                    ));
                }
                Ok(n) => {
                    offset += n as u64;
                    buf = &buf[n..];
                }
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        Ok(())
    }
}

// Criterion does not support custom command-line options, so read from an
// environment variable.
fn get_tempfile_path<T: AsRef<Path>>(name: T) -> PathBuf {
    match std::env::var_os("EDENFS_BENCHMARK_DIR") {
        Some(val) => PathBuf::from(val).join(name),
        None => PathBuf::from(name.as_ref()),
    }
}

fn random_4k_reads_direct(b: &mut Bencher) {
    let mut rng = thread_rng();
    let mut page = [0u8; PAGE_SIZE];

    let path = get_tempfile_path("random_reads.tmp");

    let mut options = OpenOptions::new()
        .read(true)
        .write(true)
        .create_new(true)
        .clone();
    #[cfg(target_os = "linux")]
    let options = options.custom_flags(libc::O_DIRECT);
    #[cfg(windows)]
    let options = options.custom_flags(winapi::um::winbase::FILE_FLAG_NO_BUFFERING);
    let file = options
        .open(&path)
        .expect(&format!("failed to open {}", path.display()));

    #[cfg(target_vendor = "apple")]
    unsafe {
        libc::fcntl(file.as_raw_fd(), libc::F_NOCACHE, 1);
    }

    std::fs::remove_file(&path).expect(&format!("failed to remove {}", path.display()));

    const FILE_SIZE: u64 = 20 * (1 << 30); // 20 GiB

    file.set_len(FILE_SIZE).expect("failed to set file size");

    const OFFSET_COUNT: usize = (FILE_SIZE / PAGE_SIZE as u64) as usize;
    let mut offsets = Vec::with_capacity(OFFSET_COUNT);
    for i in 0..OFFSET_COUNT {
        offsets.push((i * PAGE_SIZE) as u64);
    }
    offsets.shuffle(&mut rng);

    let mut offset_idx: usize = 0;

    b.iter(|| {
        let offset = offsets[offset_idx];
        offset_idx += 1;
        if offset_idx == offsets.len() {
            offset_idx = 0;
        }
        file.read_full_at(&mut page, offset)
            .expect("failed to write_full_at");
    });
}

fn random_4k_writes(b: &mut Bencher) {
    let mut rng = thread_rng();
    let mut page = [0; PAGE_SIZE];
    rng.fill(&mut page);

    let path = get_tempfile_path("random_writes.tmp");

    let file = File::create(&path).expect(&format!("failed to open {}", path.display()));
    std::fs::remove_file(&path).expect(&format!("failed to remove {}", path.display()));

    file.set_len(DEFAULT_FILE_SIZE)
        .expect("failed to set file size");

    let mut offsets = [0; (DEFAULT_FILE_SIZE / PAGE_SIZE as u64) as usize];
    let mut i: u64 = 0;
    for offset in &mut offsets {
        *offset = i;
        i += PAGE_SIZE as u64;
    }
    offsets.shuffle(&mut rng);

    let mut offset_idx: usize = 0;
    b.iter(|| {
        let offset = offsets[offset_idx];
        offset_idx += 1;
        if offset_idx == offsets.len() {
            offset_idx = 0;
        }
        file.write_full_at(&page, offset)
            .expect("failed to write_full_at");
    });
}

fn random_4k(c: &mut Criterion) {
    let mut group = c.benchmark_group("throughput");
    group.throughput(Throughput::Bytes(PAGE_SIZE as u64));
    group.bench_function("random_4k_reads_direct", random_4k_reads_direct);
    group.bench_function("random_4k_writes", random_4k_writes);

    // glibc's pthread_cancel implementation causes a pair of atomic cmpxchg
    // operations per syscall. If pthread cancellation is disabled, the
    // implementation becomes slightly cheaper. It appears Rust automatically
    // disables pthread cancellation on a particular tested CentOS machine,
    // but try benchmarking anyway:
    if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
        group.bench_function("random_4k_reads_direct_no_pthread", |b| {
            pthread::without_pthread_cancellation(|| random_4k_reads_direct(b))
        });
        group.bench_function("random_4k_writes_no_pthread", |b| {
            pthread::without_pthread_cancellation(|| random_4k_writes(b))
        });
    }
    group.finish();
}

#[allow(dead_code)]
fn print_current_exe_path() {
    // Buck2 makes it hard to see where the binary is, so print our exe path like Google Benchmark does for convenience.
    let path = match std::env::current_exe() {
        Ok(path) => path,
        Err(_) => {
            return;
        }
    };
    eprintln!("{}", path.display());
}

criterion_group!(main_group, random_4k);
criterion_main!(main_group);