<commit_before>/*
 * 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 crate::{
    connections::ConnectionInterface,
    handle_fields::{build_handle_field_directive, HandleFieldDirectiveValues},
};
use common::{Diagnostic, DiagnosticsResult, NamedItem};
use graphql_ir::{
    Field, FragmentDefinition, LinkedField, Program, ScalarField, Selection, Transformed,
    Transformer,
};
use intern::string_key::{Intern, StringKey};
use lazy_static::lazy_static;
use schema::{SDLSchema, Schema, Type};
use std::sync::Arc;
use thiserror::Error;

pub fn transform_declarative_connection(
    program: &Program,
    connection_interface: &ConnectionInterface,
) -> DiagnosticsResult<Program> {
    let mut transform = DeclarativeConnectionMutationTransform::new(program, connection_interface);
    let next_program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());

    if transform.errors.is_empty() {
        Ok(next_program)
    } else {
        Err(transform.errors)
    }
}

lazy_static! {
    static ref APPEND_EDGE: StringKey = "appendEdge".intern();
    static ref APPEND_NODE: StringKey = "appendNode".intern();
    static ref CONNECTIONS_ARG_NAME: StringKey = "connections".intern();
    static ref DELETE_RECORD: StringKey = "deleteRecord".intern();
    static ref DELETE_EDGE: StringKey = "deleteEdge".intern();
    static ref PREPEND_EDGE: StringKey = "prependEdge".intern();
    static ref PREPEND_NODE: StringKey = "prependNode".intern();
    static ref EDGE_TYPENAME_ARG: StringKey = "edgeTypeName".intern();
}

struct DeclarativeConnectionMutationTransform<'a> {
    schema: &'a SDLSchema,
    errors: Vec<Diagnostic>,
    connection_interface: &'a ConnectionInterface,
}

impl<'a> DeclarativeConnectionMutationTransform<'a> {
    fn new(program: &'a Program, connection_interface: &'a ConnectionInterface) -> Self {
        Self {
            schema: &program.schema,
            connection_interface,
            errors: vec![],
        }
    }
}

impl Transformer for DeclarativeConnectionMutationTransform<'_> {
    const NAME: &'static str = "DeclarativeConnectionMutationTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_fragment(&mut self, _: &FragmentDefinition) -> Transformed<FragmentDefinition> {
        Transformed::Keep
    }

    fn transform_scalar_field(&mut self, field: &ScalarField) -> Transformed<Selection> {
        let linked_field_directive = field.directives.iter().find(|directive| {
            directive.name.item == *APPEND_EDGE
                || directive.name.item == *PREPEND_EDGE
                || directive.name.item == *APPEND_NODE
                || directive.name.item == *PREPEND_NODE
        });
        if let Some(linked_field_directive) = linked_field_directive {
            self.errors.push(Diagnostic::error(
                ValidationMessage::ConnectionMutationDirectiveOnScalarField {
                    directive_name: linked_field_directive.name.item,
                    field_name: field.alias_or_name(self.schema),
                },
                field.definition.location,
            ));
        }
        let delete_directive = field.directives.iter().find(|directive| {
            directive.name.item == *DELETE_RECORD || directive.name.item == *DELETE_EDGE
        });
        let field_definition = self.schema.field(field.definition.item);
        match delete_directive {
            None => Transformed::Keep,
            Some(delete_directive) => {
                let is_id = self.schema.is_id(field_definition.type_.inner());
                if !is_id {
                    self.errors.push(Diagnostic::error(
                        ValidationMessage::DeleteRecordDirectiveOnUnsupportedType {
                            directive_name: delete_directive.name.item,
                            field_name: field.alias_or_name(self.schema),
                            current_type: self.schema.get_type_string(&field_definition.type_),
                        },
                        delete_directive.name.location,
                    ));
                    Transformed::Keep
                } else {
                    let connections_arg = delete_directive.arguments.named(*CONNECTIONS_ARG_NAME);
                    let handle_directive =
                        build_handle_field_directive(HandleFieldDirectiveValues {
                            handle: delete_directive.name.item,
                            key: "".intern(),
                            dynamic_key: None,
                            filters: None,
                            handle_args: connections_arg
                                .map(|connections_arg| vec![connections_arg.clone()]),
                        });
                    let mut next_directives: Vec<_> = field
                        .directives
                        .iter()
                        .filter(|directive| directive.name.item != *DELETE_RECORD)
                        .cloned()
                        .collect();
                    next_directives.push(handle_directive);
                    Transformed::Replace(Selection::ScalarField(Arc::new(ScalarField {
                        directives: next_directives,
                        ..field.clone()
                    })))
                }
            }
        }
    }

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        let transformed_field = self.default_transform_linked_field(field);
        let delete_directive = field.directives.named(*DELETE_RECORD);
        if let Some(delete_directive) = delete_directive {
            self.errors.push(Diagnostic::error(
                ValidationMessage::DeleteRecordDirectiveOnLinkedField {
                    directive_name: delete_directive.name.item,
                    field_name: field.alias_or_name(self.schema),
                },
                field.definition.location,
            ));
        }
        let edge_directive = field.directives.iter().find(|directive| {
            directive.name.item == *APPEND_EDGE || directive.name.item == *PREPEND_EDGE
        });
        let node_directive = field.directives.iter().find(|directive| {
            directive.name.item == *APPEND_NODE || directive.name.item == *PREPEND_NODE
        });
        match (edge_directive, node_directive) {
            (Some(edge_directive), Some(node_directive)) => {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::ConflictingEdgeAndNodeDirectives {
                        edge_directive_name: edge_directive.name.item,
                        node_directive_name: node_directive.name.item,
                        field_name: field.alias_or_name(self.schema),
                    },
                    edge_directive.name.location,
                ));
                transformed_field
            }
            (None, None) => transformed_field,
            (Some(edge_directive), None) => {
                let connections_arg = edge_directive.arguments.named(*CONNECTIONS_ARG_NAME);
                match connections_arg {
                    None => {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::ConnectionsArgumentRequired {
                                directive_name: edge_directive.name.item,
                            },
                            edge_directive.name.location,
                        ));
                        transformed_field
                    }
                    Some(connections_arg) => {
                        let field_definition = self.schema.field(field.definition.item);
                        let mut has_cursor_field = false;
                        let mut has_node_field = false;
                        if let Type::Object(id) = field_definition.type_.inner() {
                            let object = self.schema.object(id);
                            for field_id in &object.fields {
                                let current_field_name = self.schema.field(*field_id).name.item;
                                if current_field_name == self.connection_interface.cursor {
                                    has_cursor_field = true;
                                } else if current_field_name == self.connection_interface.node {
                                    has_node_field = true;
                                }
                            }
                        }
                        if has_cursor_field && has_node_field {
                            let handle_directive =
                                build_handle_field_directive(HandleFieldDirectiveValues {
                                    handle: edge_directive.name.item,
                                    key: "".intern(),
                                    dynamic_key: None,
                                    filters: None,
                                    handle_args: Some(vec![connections_arg.clone()]),
                                });
                            let mut next_field = match transformed_field {
                                Transformed::Replace(Selection::LinkedField(linked_field)) => {
                                    (*linked_field).clone()
                                }
                                Transformed::Keep => field.clone(),
                                _ => panic!(
                                    "DeclarativeConnection got unexpected transform result: `{:?}`.",
                                    transformed_field
                                ),
                            };
                            let index = next_field
                                .directives
                                .iter()
                                .position(|directive| {
                                    directive.name.item == edge_directive.name.item
                                })
                                .expect("Expected the edge directive to exist.");
                            next_field.directives[index] = handle_directive;
                            Transformed::Replace(Selection::LinkedField(Arc::new(next_field)))
                        } else {
                            self.errors.push(Diagnostic::error(
                                ValidationMessage::EdgeDirectiveOnUnsupportedType {
                                    directive_name: edge_directive.name.item,
                                    field_name: field.alias_or_name(self.schema),
                                },
                                edge_directive.name.location,
                            ));
                            Transformed::Keep
                        }
                    }
                }
            }
            (None, Some(node_directive)) => {
                let connections_arg = node_directive.arguments.named(*CONNECTIONS_ARG_NAME);
                match connections_arg {
                    None => {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::ConnectionsArgumentRequired {
                                directive_name: node_directive.name.item,
                            },
                            node_directive.name.location,
                        ));
                        transformed_field
                    }
                    Some(connections_arg) => {
                        let edge_typename_arg = node_directive.arguments.named(*EDGE_TYPENAME_ARG);
                        if let Some(edge_typename_arg) = edge_typename_arg {
                            let field_definition = self.schema.field(field.definition.item);
                            match field_definition.type_.inner() {
                                Type::Object(_) | Type::Interface(_) | Type::Union(_) => {
                                    let handle_directive =
                                        build_handle_field_directive(HandleFieldDirectiveValues {
                                            handle: node_directive.name.item,
                                            key: "".intern(),
                                            dynamic_key: None,
                                            filters: None,
                                            handle_args: Some(vec![
                                                connections_arg.clone(),
                                                edge_typename_arg.clone(),
                                            ]),
                                        });
                                    let mut next_field = match transformed_field {
                                        Transformed::Replace(Selection::LinkedField(
                                            linked_field,
                                        )) => (*linked_field).clone(),
                                        Transformed::Keep => field.clone(),
                                        _ => panic!(
                                            "DeclarativeConnection got unexpected transform result: `{:?}`.",
                                            transformed_field
                                        ),
                                    };
                                    let index = next_field
                                        .directives
                                        .iter()
                                        .position(|directive| {
                                            directive.name.item == node_directive.name.item
                                        })
                                        .expect("Expected the edge directive to exist.");
                                    next_field.directives[index] = handle_directive;
                                    Transformed::Replace(Selection::LinkedField(Arc::new(
                                        next_field,
                                    )))
                                }
                                _ => {
                                    self.errors.push(Diagnostic::error(
                                        ValidationMessage::NodeDirectiveOnUnsupportedType {
                                            directive_name: node_directive.name.item,
                                            field_name: field.alias_or_name(self.schema),
                                            current_type: self
                                                .schema
                                                .get_type_string(&field_definition.type_),
                                        },
                                        node_directive.name.location,
                                    ));
                                    Transformed::Keep
                                }
                            }
                        } else {
                            self.errors.push(Diagnostic::error(
                                ValidationMessage::NodeDirectiveMissesRequiredEdgeTypeName {
                                    directive_name: node_directive.name.item,
                                    field_name: field.alias_or_name(self.schema),
                                },
                                node_directive.name.location,
                            ));
                            Transformed::Keep
                        }
                    }
                }
            }
        }
    }
}

#[derive(Debug, Error)]
enum ValidationMessage {
    #[error(
        "Unsupported use of @{directive_name} on field '${field_name}', 'edgeTypeName' argument must be provided."
    )]
    NodeDirectiveMissesRequiredEdgeTypeName {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error("Invalid use of @{directive_name} on scalar field '{field_name}'.")]
    ConnectionMutationDirectiveOnScalarField {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error(
        "Invalid use of @{directive_name} on field '{field_name}'. Expected field type 'ID', got '{current_type}'."
    )]
    DeleteRecordDirectiveOnUnsupportedType {
        directive_name: StringKey,
        field_name: StringKey,
        current_type: String,
    },

    #[error("Invalid use of @{directive_name} on linked field '{field_name}'.")]
    DeleteRecordDirectiveOnLinkedField {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error(
        "Invalid use of @{edge_directive_name} and @{node_directive_name} on field '{field_name}' - these directives cannot be used together."
    )]
    ConflictingEdgeAndNodeDirectives {
        edge_directive_name: StringKey,
        node_directive_name: StringKey,
        field_name: StringKey,
    },

    #[error("Expected the 'connections' argument to be defined on @{directive_name}.")]
    ConnectionsArgumentRequired { directive_name: StringKey },

    #[error(
        "Unsupported use of @{directive_name} on field '{field_name}', expected an edge field (a field with 'cursor' and 'node' selection)."
    )]
    EdgeDirectiveOnUnsupportedType {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error(
        "Unsupported use of @{directive_name} on field '{field_name}'. Expected an object, union or interface, but got '{current_type}'."
    )]
    NodeDirectiveOnUnsupportedType {
        directive_name: StringKey,
        field_name: StringKey,
        current_type: String,
    },
}
<commit_msg>declarative_connection: intern empty string once<commit_after>/*
 * 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 crate::{
    connections::ConnectionInterface,
    handle_fields::{build_handle_field_directive, HandleFieldDirectiveValues},
};
use common::{Diagnostic, DiagnosticsResult, NamedItem};
use graphql_ir::{
    Field, FragmentDefinition, LinkedField, Program, ScalarField, Selection, Transformed,
    Transformer,
};
use intern::string_key::{Intern, StringKey};
use lazy_static::lazy_static;
use schema::{SDLSchema, Schema, Type};
use std::sync::Arc;
use thiserror::Error;

pub fn transform_declarative_connection(
    program: &Program,
    connection_interface: &ConnectionInterface,
) -> DiagnosticsResult<Program> {
    let mut transform = DeclarativeConnectionMutationTransform::new(program, connection_interface);
    let next_program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());

    if transform.errors.is_empty() {
        Ok(next_program)
    } else {
        Err(transform.errors)
    }
}

lazy_static! {
    static ref APPEND_EDGE: StringKey = "appendEdge".intern();
    static ref APPEND_NODE: StringKey = "appendNode".intern();
    static ref CONNECTIONS_ARG_NAME: StringKey = "connections".intern();
    static ref DELETE_RECORD: StringKey = "deleteRecord".intern();
    static ref DELETE_EDGE: StringKey = "deleteEdge".intern();
    static ref PREPEND_EDGE: StringKey = "prependEdge".intern();
    static ref PREPEND_NODE: StringKey = "prependNode".intern();
    static ref EDGE_TYPENAME_ARG: StringKey = "edgeTypeName".intern();
    static ref EMPTY_STRING: StringKey = "".intern();
}

struct DeclarativeConnectionMutationTransform<'a> {
    schema: &'a SDLSchema,
    errors: Vec<Diagnostic>,
    connection_interface: &'a ConnectionInterface,
}

impl<'a> DeclarativeConnectionMutationTransform<'a> {
    fn new(program: &'a Program, connection_interface: &'a ConnectionInterface) -> Self {
        Self {
            schema: &program.schema,
            connection_interface,
            errors: vec![],
        }
    }
}

impl Transformer for DeclarativeConnectionMutationTransform<'_> {
    const NAME: &'static str = "DeclarativeConnectionMutationTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_fragment(&mut self, _: &FragmentDefinition) -> Transformed<FragmentDefinition> {
        Transformed::Keep
    }

    fn transform_scalar_field(&mut self, field: &ScalarField) -> Transformed<Selection> {
        let linked_field_directive = field.directives.iter().find(|directive| {
            directive.name.item == *APPEND_EDGE
                || directive.name.item == *PREPEND_EDGE
                || directive.name.item == *APPEND_NODE
                || directive.name.item == *PREPEND_NODE
        });
        if let Some(linked_field_directive) = linked_field_directive {
            self.errors.push(Diagnostic::error(
                ValidationMessage::ConnectionMutationDirectiveOnScalarField {
                    directive_name: linked_field_directive.name.item,
                    field_name: field.alias_or_name(self.schema),
                },
                field.definition.location,
            ));
        }
        let delete_directive = field.directives.iter().find(|directive| {
            directive.name.item == *DELETE_RECORD || directive.name.item == *DELETE_EDGE
        });
        let field_definition = self.schema.field(field.definition.item);
        match delete_directive {
            None => Transformed::Keep,
            Some(delete_directive) => {
                let is_id = self.schema.is_id(field_definition.type_.inner());
                if !is_id {
                    self.errors.push(Diagnostic::error(
                        ValidationMessage::DeleteRecordDirectiveOnUnsupportedType {
                            directive_name: delete_directive.name.item,
                            field_name: field.alias_or_name(self.schema),
                            current_type: self.schema.get_type_string(&field_definition.type_),
                        },
                        delete_directive.name.location,
                    ));
                    Transformed::Keep
                } else {
                    let connections_arg = delete_directive.arguments.named(*CONNECTIONS_ARG_NAME);
                    let handle_directive =
                        build_handle_field_directive(HandleFieldDirectiveValues {
                            handle: delete_directive.name.item,
                            key: *EMPTY_STRING,
                            dynamic_key: None,
                            filters: None,
                            handle_args: connections_arg
                                .map(|connections_arg| vec![connections_arg.clone()]),
                        });
                    let mut next_directives: Vec<_> = field
                        .directives
                        .iter()
                        .filter(|directive| directive.name.item != *DELETE_RECORD)
                        .cloned()
                        .collect();
                    next_directives.push(handle_directive);
                    Transformed::Replace(Selection::ScalarField(Arc::new(ScalarField {
                        directives: next_directives,
                        ..field.clone()
                    })))
                }
            }
        }
    }

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        let transformed_field = self.default_transform_linked_field(field);
        let delete_directive = field.directives.named(*DELETE_RECORD);
        if let Some(delete_directive) = delete_directive {
            self.errors.push(Diagnostic::error(
                ValidationMessage::DeleteRecordDirectiveOnLinkedField {
                    directive_name: delete_directive.name.item,
                    field_name: field.alias_or_name(self.schema),
                },
                field.definition.location,
            ));
        }
        let edge_directive = field.directives.iter().find(|directive| {
            directive.name.item == *APPEND_EDGE || directive.name.item == *PREPEND_EDGE
        });
        let node_directive = field.directives.iter().find(|directive| {
            directive.name.item == *APPEND_NODE || directive.name.item == *PREPEND_NODE
        });
        match (edge_directive, node_directive) {
            (Some(edge_directive), Some(node_directive)) => {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::ConflictingEdgeAndNodeDirectives {
                        edge_directive_name: edge_directive.name.item,
                        node_directive_name: node_directive.name.item,
                        field_name: field.alias_or_name(self.schema),
                    },
                    edge_directive.name.location,
                ));
                transformed_field
            }
            (None, None) => transformed_field,
            (Some(edge_directive), None) => {
                let connections_arg = edge_directive.arguments.named(*CONNECTIONS_ARG_NAME);
                match connections_arg {
                    None => {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::ConnectionsArgumentRequired {
                                directive_name: edge_directive.name.item,
                            },
                            edge_directive.name.location,
                        ));
                        transformed_field
                    }
                    Some(connections_arg) => {
                        let field_definition = self.schema.field(field.definition.item);
                        let mut has_cursor_field = false;
                        let mut has_node_field = false;
                        if let Type::Object(id) = field_definition.type_.inner() {
                            let object = self.schema.object(id);
                            for field_id in &object.fields {
                                let current_field_name = self.schema.field(*field_id).name.item;
                                if current_field_name == self.connection_interface.cursor {
                                    has_cursor_field = true;
                                } else if current_field_name == self.connection_interface.node {
                                    has_node_field = true;
                                }
                            }
                        }
                        if has_cursor_field && has_node_field {
                            let handle_directive =
                                build_handle_field_directive(HandleFieldDirectiveValues {
                                    handle: edge_directive.name.item,
                                    key: *EMPTY_STRING,
                                    dynamic_key: None,
                                    filters: None,
                                    handle_args: Some(vec![connections_arg.clone()]),
                                });
                            let mut next_field = match transformed_field {
                                Transformed::Replace(Selection::LinkedField(linked_field)) => {
                                    (*linked_field).clone()
                                }
                                Transformed::Keep => field.clone(),
                                _ => panic!(
                                    "DeclarativeConnection got unexpected transform result: `{:?}`.",
                                    transformed_field
                                ),
                            };
                            let index = next_field
                                .directives
                                .iter()
                                .position(|directive| {
                                    directive.name.item == edge_directive.name.item
                                })
                                .expect("Expected the edge directive to exist.");
                            next_field.directives[index] = handle_directive;
                            Transformed::Replace(Selection::LinkedField(Arc::new(next_field)))
                        } else {
                            self.errors.push(Diagnostic::error(
                                ValidationMessage::EdgeDirectiveOnUnsupportedType {
                                    directive_name: edge_directive.name.item,
                                    field_name: field.alias_or_name(self.schema),
                                },
                                edge_directive.name.location,
                            ));
                            Transformed::Keep
                        }
                    }
                }
            }
            (None, Some(node_directive)) => {
                let connections_arg = node_directive.arguments.named(*CONNECTIONS_ARG_NAME);
                match connections_arg {
                    None => {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::ConnectionsArgumentRequired {
                                directive_name: node_directive.name.item,
                            },
                            node_directive.name.location,
                        ));
                        transformed_field
                    }
                    Some(connections_arg) => {
                        let edge_typename_arg = node_directive.arguments.named(*EDGE_TYPENAME_ARG);
                        if let Some(edge_typename_arg) = edge_typename_arg {
                            let field_definition = self.schema.field(field.definition.item);
                            match field_definition.type_.inner() {
                                Type::Object(_) | Type::Interface(_) | Type::Union(_) => {
                                    let handle_directive =
                                        build_handle_field_directive(HandleFieldDirectiveValues {
                                            handle: node_directive.name.item,
                                            key: *EMPTY_STRING,
                                            dynamic_key: None,
                                            filters: None,
                                            handle_args: Some(vec![
                                                connections_arg.clone(),
                                                edge_typename_arg.clone(),
                                            ]),
                                        });
                                    let mut next_field = match transformed_field {
                                        Transformed::Replace(Selection::LinkedField(
                                            linked_field,
                                        )) => (*linked_field).clone(),
                                        Transformed::Keep => field.clone(),
                                        _ => panic!(
                                            "DeclarativeConnection got unexpected transform result: `{:?}`.",
                                            transformed_field
                                        ),
                                    };
                                    let index = next_field
                                        .directives
                                        .iter()
                                        .position(|directive| {
                                            directive.name.item == node_directive.name.item
                                        })
                                        .expect("Expected the edge directive to exist.");
                                    next_field.directives[index] = handle_directive;
                                    Transformed::Replace(Selection::LinkedField(Arc::new(
                                        next_field,
                                    )))
                                }
                                _ => {
                                    self.errors.push(Diagnostic::error(
                                        ValidationMessage::NodeDirectiveOnUnsupportedType {
                                            directive_name: node_directive.name.item,
                                            field_name: field.alias_or_name(self.schema),
                                            current_type: self
                                                .schema
                                                .get_type_string(&field_definition.type_),
                                        },
                                        node_directive.name.location,
                                    ));
                                    Transformed::Keep
                                }
                            }
                        } else {
                            self.errors.push(Diagnostic::error(
                                ValidationMessage::NodeDirectiveMissesRequiredEdgeTypeName {
                                    directive_name: node_directive.name.item,
                                    field_name: field.alias_or_name(self.schema),
                                },
                                node_directive.name.location,
                            ));
                            Transformed::Keep
                        }
                    }
                }
            }
        }
    }
}

#[derive(Debug, Error)]
enum ValidationMessage {
    #[error(
        "Unsupported use of @{directive_name} on field '${field_name}', 'edgeTypeName' argument must be provided."
    )]
    NodeDirectiveMissesRequiredEdgeTypeName {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error("Invalid use of @{directive_name} on scalar field '{field_name}'.")]
    ConnectionMutationDirectiveOnScalarField {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error(
        "Invalid use of @{directive_name} on field '{field_name}'. Expected field type 'ID', got '{current_type}'."
    )]
    DeleteRecordDirectiveOnUnsupportedType {
        directive_name: StringKey,
        field_name: StringKey,
        current_type: String,
    },

    #[error("Invalid use of @{directive_name} on linked field '{field_name}'.")]
    DeleteRecordDirectiveOnLinkedField {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error(
        "Invalid use of @{edge_directive_name} and @{node_directive_name} on field '{field_name}' - these directives cannot be used together."
    )]
    ConflictingEdgeAndNodeDirectives {
        edge_directive_name: StringKey,
        node_directive_name: StringKey,
        field_name: StringKey,
    },

    #[error("Expected the 'connections' argument to be defined on @{directive_name}.")]
    ConnectionsArgumentRequired { directive_name: StringKey },

    #[error(
        "Unsupported use of @{directive_name} on field '{field_name}', expected an edge field (a field with 'cursor' and 'node' selection)."
    )]
    EdgeDirectiveOnUnsupportedType {
        directive_name: StringKey,
        field_name: StringKey,
    },

    #[error(
        "Unsupported use of @{directive_name} on field '{field_name}'. Expected an object, union or interface, but got '{current_type}'."
    )]
    NodeDirectiveOnUnsupportedType {
        directive_name: StringKey,
        field_name: StringKey,
        current_type: String,
    },
}
<|endoftext|>


<commit_before>#![cfg(feature = "interchange")]

use crate::bba::idxset::IndexedMap;
use crate::sites::*;
use crate::interchange_gen::routing_graph::*;
use crate::interchange_gen::bel_pin_map::get_pin_maps;

use crate::schema::*;
use crate::chip::Chip;
use crate::database::Database;
use crate::bba::idstring::*;
use crate::bels::PinDir;

use std::convert::TryInto;

use flate2::Compression;
use flate2::write::GzEncoder;

pub fn write(c: &Chip, _db: &mut Database, ids: &mut IdStringDB, graph: &IcGraph, filename: &str) -> ::capnp::Result<()> {
    let mut m = ::capnp::message::Builder::new_default();
    {
        let mut dev = m.init_root::<DeviceResources_capnp::device::Builder>();
        let mut uniq_site_types = IndexedMap::<String, Site>::new();
        dev.set_name(&c.device);
        {
            let mut tiletypes = dev.reborrow().init_tile_type_list(graph.tile_types.len().try_into().unwrap());
            for (i, (_, data)) in graph.tile_types.iter().enumerate() {
                let mut tt = tiletypes.reborrow().get(i.try_into().unwrap());
                // TODO: form a nice name from the constituent tile types
                // TODO: remove the NULL hack
                if i == graph.tiles[0].type_idx {
                    tt.set_name(ids.id("NULL").val().try_into().unwrap());
                } else {
                    tt.set_name(ids.id(&format!("tiletype_{}", i)).val().try_into().unwrap());
                }
                {
                    let mut sites = tt.reborrow().init_site_types(data.site_types.len().try_into().unwrap());
                    for (j, site_type) in data.site_types.iter().enumerate() {
                        // TODO: shared site types across multiple tile types?
                        let type_idx = uniq_site_types.add(&site_type.site_type, site_type.clone());
                        let mut site = sites.reborrow().get(j.try_into().unwrap());
                        site.set_primary_type(type_idx.try_into().unwrap());
                        {
                            let mut pins = site.init_primary_pins_to_tile_wires(site_type.pins.len().try_into().unwrap());
                            for (k, site_pin) in site_type.pins.iter().enumerate() {
                                pins.set(k.try_into().unwrap(), ids.id(&site_pin.tile_wire).val().try_into().unwrap());
                            }
                        }
                    }
                }
                {
                    let mut wires = tt.reborrow().init_wires(data.wires.len().try_into().unwrap());
                    for (j, (_, wire_data)) in data.wires.iter().enumerate() {
                        wires.set(j.try_into().unwrap(),  wire_data.name.val().try_into().unwrap());
                    }
                }
                {
                    let mut pips = tt.reborrow().init_pips(data.pips.len().try_into().unwrap());
                    for (j, pip_data) in data.pips.iter().enumerate() {
                        let mut p = pips.reborrow().get(j.try_into().unwrap());
                        p.set_wire0(pip_data.src_wire.try_into().unwrap());
                        p.set_wire1(pip_data.dst_wire.try_into().unwrap());
                        p.set_directional(true);
                        p.set_buffered20(true);
                        p.set_buffered21(false);
                        p.set_conventional(());
                    }
                }
                // TODO: constant sources
            }
        }
        {
            // Site types
            let mut sitetypes = dev.reborrow().init_site_type_list(uniq_site_types.len().try_into().unwrap());
            for (i, (_, data)) in uniq_site_types.iter().enumerate() {
                let mut st = sitetypes.reborrow().get(i.try_into().unwrap());
                st.set_name(ids.id(&data.site_type).val().try_into().unwrap());
                st.set_last_input(data.last_input_pin.try_into().unwrap());
                {
                    let mut bels = st.reborrow().init_bels(data.bels.len().try_into().unwrap());
                    for (j, bel_data) in data.bels.iter().enumerate() {
                        let mut bel = bels.reborrow().get(j.try_into().unwrap());
                        bel.set_name(ids.id(&bel_data.name).val().try_into().unwrap());
                        bel.set_type(ids.id(&bel_data.bel_type).val().try_into().unwrap());
                        bel.set_category(match bel_data.bel_class {
                            SiteBelClass::BEL => DeviceResources_capnp::device::BELCategory::Logic,
                            SiteBelClass::RBEL => DeviceResources_capnp::device::BELCategory::Routing,
                            SiteBelClass::PORT => DeviceResources_capnp::device::BELCategory::SitePort,
                        });
                        bel.set_non_inverting(());
                        {
                            let mut bel_pins = bel.reborrow().init_pins(bel_data.pins.len().try_into().unwrap());
                            for (k, pin) in bel_data.pins.iter().enumerate() {
                                bel_pins.set(k.try_into().unwrap(), (*pin).try_into().unwrap());
                            }
                        }
                    }
                }
                {
                    let mut bel_pins = st.reborrow().init_bel_pins(data.bel_pins.len().try_into().unwrap());
                    for (j, pin_data) in data.bel_pins.iter().enumerate() {
                        let mut bp = bel_pins.reborrow().get(j.try_into().unwrap());
                        bp.set_name(ids.id(&pin_data.pin_name).val().try_into().unwrap());
                        bp.set_dir(match pin_data.dir {
                            PinDir::INPUT => LogicalNetlist_capnp::netlist::Direction::Input,
                            PinDir::OUTPUT => LogicalNetlist_capnp::netlist::Direction::Output,
                            PinDir::INOUT => LogicalNetlist_capnp::netlist::Direction::Inout,
                        });
                        bp.set_bel(ids.id(&pin_data.bel_name).val().try_into().unwrap());
                    }
                }
                {
                    let mut wires = st.reborrow().init_site_wires(data.wires.len().try_into().unwrap());
                    for (j, wire_data) in data.wires.iter().enumerate() {
                        let mut w = wires.reborrow().get(j.try_into().unwrap());
                        w.set_name(ids.id(&wire_data.name).val().try_into().unwrap());
                        {
                            let mut bel_pins = w.reborrow().init_pins(wire_data.bel_pins.len().try_into().unwrap());
                            for (k, pin) in wire_data.bel_pins.iter().enumerate() {
                                bel_pins.set(k.try_into().unwrap(), (*pin).try_into().unwrap());
                            }
                        }
                    }
                }
                {
                    let mut pips = st.reborrow().init_site_p_i_ps(data.pips.len().try_into().unwrap());
                    for (j, pip_data) in data.pips.iter().enumerate() {
                        let mut p = pips.reborrow().get(j.try_into().unwrap());
                        p.set_inpin(pip_data.in_pin.try_into().unwrap());
                        p.set_outpin(pip_data.out_pin.try_into().unwrap());
                    }
                }
                {
                    let mut pins = st.reborrow().init_pins(data.pins.len().try_into().unwrap());
                    for (j, pin_data) in data.pins.iter().enumerate() {
                        let mut p = pins.reborrow().get(j.try_into().unwrap());
                        p.set_name(ids.id(&pin_data.site_wire).val().try_into().unwrap());
                        p.set_dir(match pin_data.dir {
                            PinDir::INPUT => LogicalNetlist_capnp::netlist::Direction::Input,
                            PinDir::OUTPUT => LogicalNetlist_capnp::netlist::Direction::Output,
                            PinDir::INOUT => LogicalNetlist_capnp::netlist::Direction::Inout,
                        });
                        p.set_belpin(pin_data.bel_pin.try_into().unwrap());
                    }
                }
            }
        }
        let mut wire_list = Vec::new();
        {
            // this wire_list is the list of wires as we create nodes, to be output as dev.wires later
            let mut nodes = dev.reborrow().init_nodes(graph.nodes.len().try_into().unwrap());
            for (i, node_data) in graph.nodes.iter().enumerate() {
                let n = nodes.reborrow().get(i.try_into().unwrap());
                let mut node_wires = n.init_wires(node_data.wires.len().try_into().unwrap());
                // write root wire
                {
                    node_wires.set(0, wire_list.len().try_into().unwrap());
                    wire_list.push((node_data.root_wire.tile_name, node_data.root_wire.wire_name));
                }
                let mut node_wire_idx = 1;
                // write non-root wires
                for wire in node_data.wires.iter().filter(|w| **w != node_data.root_wire) {
                    node_wires.set(node_wire_idx, wire_list.len().try_into().unwrap());
                    wire_list.push((wire.tile_name, wire.wire_name));
                    node_wire_idx += 1;
                }
            }
        }
        {
            let mut wires = dev.reborrow().init_wires(wire_list.len().try_into().unwrap());
            for (i, (tile_name, wire_name)) in wire_list.iter().enumerate() {
                let mut w = wires.reborrow().get(i.try_into().unwrap());
                w.set_tile(tile_name.val().try_into().unwrap());
                w.set_wire(wire_name.val().try_into().unwrap());
            }
        }
        {
            let mut tiles = dev.reborrow().init_tile_list(graph.tiles.len().try_into().unwrap());
            for (i, tile_data) in graph.tiles.iter().enumerate() {
                let mut t = tiles.reborrow().get(i.try_into().unwrap());
                t.set_name(tile_data.name.val().try_into().unwrap());
                t.set_type(tile_data.type_idx.try_into().unwrap());
                t.set_row(tile_data.y.try_into().unwrap());
                t.set_col(tile_data.x.try_into().unwrap());
                {
                    let tt = graph.tile_types.value(tile_data.type_idx);
                    let mut sites = t.reborrow().init_sites(tt.site_types.len().try_into().unwrap());
                    for (j, site_data) in tt.site_types.iter().enumerate() {
                        let mut s = sites.reborrow().get(j.try_into().unwrap());
                        s.set_name(ids.id(&format!("R{}C{}_{}", tile_data.y, tile_data.x, &site_data.name)).val().try_into().unwrap());
                        s.set_type(j.try_into().unwrap());
                    }
                }
            }
        }
        {
            let mut constants = dev.reborrow().init_constants();
            constants.set_gnd_cell_type(ids.id("VLO").val().try_into().unwrap());
            constants.set_gnd_cell_pin(ids.id("Z").val().try_into().unwrap());
            constants.set_vcc_cell_type(ids.id("VHI").val().try_into().unwrap());
            constants.set_vcc_cell_pin(ids.id("Z").val().try_into().unwrap());
        }
        {
            let mut constraints = dev.reborrow().init_constraints();
            {
                let mut cc = constraints.reborrow().init_cell_constraints(1);
                {
                    let mut lut_constr = cc.reborrow().get(0);
                    lut_constr.set_cell("LUT4");
                    let mut lut_loc = lut_constr.init_locations(1).get(0);
                    lut_loc.reborrow().init_site_types(1).set(0, "PLC");
                    {
                        let mut lut_bels = lut_loc.reborrow().init_bel().init_bels(8);
                        for i in 0..8 {
                            lut_bels.set(i,
                                &format!("SLICE{}_LUT{}", "ABCD".chars().nth((i / 2) as usize).unwrap(), i % 2));
                        }
                    }
                    lut_loc.reborrow().init_implies(0);
                }
            }
        }
        {
            let mut pin_maps = Vec::new();
            for (site_type, site) in uniq_site_types.iter() {
                pin_maps.extend(get_pin_maps(site).drain(..).map(|x| (site_type.to_string(), x)));
            }
            let mut c2b = dev.reborrow().init_cell_bel_map((2 + pin_maps.len()).try_into().unwrap());
            c2b.reborrow().get(0).set_cell(ids.id("VLO").val().try_into().unwrap());
            c2b.reborrow().get(1).set_cell(ids.id("VHI").val().try_into().unwrap());
            for (i, (site_type, pin_map_data)) in pin_maps.iter().enumerate() {
                let mut m = c2b.reborrow().get((2 + i).try_into().unwrap());
                m.set_cell(ids.id(&pin_map_data.cell_type).val().try_into().unwrap());
                let mut pin_map = m.init_common_pins(1).get(0);
                {
                    let mut st = pin_map.reborrow().init_site_types(1).get(0);
                    st.set_site_type(ids.id(&site_type).val().try_into().unwrap());
                    let mut bels = st.init_bels(pin_map_data.bels.len().try_into().unwrap());
                    for (j, bel) in pin_map_data.bels.iter().enumerate() {
                        bels.set(j.try_into().unwrap(), ids.id(&bel).val().try_into().unwrap());
                    }
                }
                {
                    let mut pins = pin_map.init_pins(pin_map_data.pin_map.len().try_into().unwrap());
                    for (j, (cell_pin, bel_pin)) in pin_map_data.pin_map.iter().enumerate() {
                        pins.reborrow().get(j.try_into().unwrap()).set_cell_pin(ids.id(cell_pin).val().try_into().unwrap());
                        pins.reborrow().get(j.try_into().unwrap()).set_bel_pin(ids.id(bel_pin).val().try_into().unwrap());
                    }
                }
            }

        }
        {
            let mut packages = dev.reborrow().init_packages(1);
            packages.reborrow().get(0).set_name(ids.id("QFN72").val().try_into().unwrap());
            // TODO: all packages and pin map
        }
        {
            let mut strs = dev.init_str_list(ids.len().try_into().unwrap());
            for i in 0..ids.len() {
                strs.set(i.try_into().unwrap(), ids.idx_str(i));
            }
        }
    }
    let mut e = GzEncoder::new(std::fs::File::create(filename)?, Compression::default());
    ::capnp::serialize::write_message(&mut e, &m)?;
    e.finish()?;
    Ok(())
}
<commit_msg>Mark Vcc wire as a constant source<commit_after>#![cfg(feature = "interchange")]

use crate::bba::idxset::IndexedMap;
use crate::sites::*;
use crate::interchange_gen::routing_graph::*;
use crate::interchange_gen::bel_pin_map::get_pin_maps;

use crate::schema::*;
use crate::chip::Chip;
use crate::database::Database;
use crate::bba::idstring::*;
use crate::bels::PinDir;

use std::convert::TryInto;

use flate2::Compression;
use flate2::write::GzEncoder;

pub fn write(c: &Chip, _db: &mut Database, ids: &mut IdStringDB, graph: &IcGraph, filename: &str) -> ::capnp::Result<()> {
    let mut m = ::capnp::message::Builder::new_default();
    {
        let mut dev = m.init_root::<DeviceResources_capnp::device::Builder>();
        let mut uniq_site_types = IndexedMap::<String, Site>::new();
        dev.set_name(&c.device);
        {
            let mut tiletypes = dev.reborrow().init_tile_type_list(graph.tile_types.len().try_into().unwrap());
            for (i, (_, data)) in graph.tile_types.iter().enumerate() {
                let mut tt = tiletypes.reborrow().get(i.try_into().unwrap());
                // TODO: form a nice name from the constituent tile types
                // TODO: remove the NULL hack
                if i == graph.tiles[0].type_idx {
                    tt.set_name(ids.id("NULL").val().try_into().unwrap());
                } else {
                    tt.set_name(ids.id(&format!("tiletype_{}", i)).val().try_into().unwrap());
                }
                {
                    let mut sites = tt.reborrow().init_site_types(data.site_types.len().try_into().unwrap());
                    for (j, site_type) in data.site_types.iter().enumerate() {
                        // TODO: shared site types across multiple tile types?
                        let type_idx = uniq_site_types.add(&site_type.site_type, site_type.clone());
                        let mut site = sites.reborrow().get(j.try_into().unwrap());
                        site.set_primary_type(type_idx.try_into().unwrap());
                        {
                            let mut pins = site.init_primary_pins_to_tile_wires(site_type.pins.len().try_into().unwrap());
                            for (k, site_pin) in site_type.pins.iter().enumerate() {
                                pins.set(k.try_into().unwrap(), ids.id(&site_pin.tile_wire).val().try_into().unwrap());
                            }
                        }
                    }
                }
                {
                    let mut wires = tt.reborrow().init_wires(data.wires.len().try_into().unwrap());
                    for (j, (_, wire_data)) in data.wires.iter().enumerate() {
                        wires.set(j.try_into().unwrap(),  wire_data.name.val().try_into().unwrap());
                    }
                }
                {
                    let mut pips = tt.reborrow().init_pips(data.pips.len().try_into().unwrap());
                    for (j, pip_data) in data.pips.iter().enumerate() {
                        let mut p = pips.reborrow().get(j.try_into().unwrap());
                        p.set_wire0(pip_data.src_wire.try_into().unwrap());
                        p.set_wire1(pip_data.dst_wire.try_into().unwrap());
                        p.set_directional(true);
                        p.set_buffered20(true);
                        p.set_buffered21(false);
                        p.set_conventional(());
                    }
                }
                {
                    let vcc_wire = ids.id("G:VCC");
                    if let Some(vcc_idx) = data.wires.get_index(&vcc_wire) {
                        // Mark the G:VCC wire in a tile, if it exists, as a source of Vcc
                        // this doesn't cover all constants but gets us started
                        let mut constant = tt.reborrow().init_constants(1).get(0);
                        constant.reborrow().init_wires(1).set(0, vcc_idx.try_into().unwrap());
                        constant.set_constant(DeviceResources_capnp::device::ConstantType::Vcc);
                    }
                }
            }
        }
        {
            // Site types
            let mut sitetypes = dev.reborrow().init_site_type_list(uniq_site_types.len().try_into().unwrap());
            for (i, (_, data)) in uniq_site_types.iter().enumerate() {
                let mut st = sitetypes.reborrow().get(i.try_into().unwrap());
                st.set_name(ids.id(&data.site_type).val().try_into().unwrap());
                st.set_last_input(data.last_input_pin.try_into().unwrap());
                {
                    let mut bels = st.reborrow().init_bels(data.bels.len().try_into().unwrap());
                    for (j, bel_data) in data.bels.iter().enumerate() {
                        let mut bel = bels.reborrow().get(j.try_into().unwrap());
                        bel.set_name(ids.id(&bel_data.name).val().try_into().unwrap());
                        bel.set_type(ids.id(&bel_data.bel_type).val().try_into().unwrap());
                        bel.set_category(match bel_data.bel_class {
                            SiteBelClass::BEL => DeviceResources_capnp::device::BELCategory::Logic,
                            SiteBelClass::RBEL => DeviceResources_capnp::device::BELCategory::Routing,
                            SiteBelClass::PORT => DeviceResources_capnp::device::BELCategory::SitePort,
                        });
                        bel.set_non_inverting(());
                        {
                            let mut bel_pins = bel.reborrow().init_pins(bel_data.pins.len().try_into().unwrap());
                            for (k, pin) in bel_data.pins.iter().enumerate() {
                                bel_pins.set(k.try_into().unwrap(), (*pin).try_into().unwrap());
                            }
                        }
                    }
                }
                {
                    let mut bel_pins = st.reborrow().init_bel_pins(data.bel_pins.len().try_into().unwrap());
                    for (j, pin_data) in data.bel_pins.iter().enumerate() {
                        let mut bp = bel_pins.reborrow().get(j.try_into().unwrap());
                        bp.set_name(ids.id(&pin_data.pin_name).val().try_into().unwrap());
                        bp.set_dir(match pin_data.dir {
                            PinDir::INPUT => LogicalNetlist_capnp::netlist::Direction::Input,
                            PinDir::OUTPUT => LogicalNetlist_capnp::netlist::Direction::Output,
                            PinDir::INOUT => LogicalNetlist_capnp::netlist::Direction::Inout,
                        });
                        bp.set_bel(ids.id(&pin_data.bel_name).val().try_into().unwrap());
                    }
                }
                {
                    let mut wires = st.reborrow().init_site_wires(data.wires.len().try_into().unwrap());
                    for (j, wire_data) in data.wires.iter().enumerate() {
                        let mut w = wires.reborrow().get(j.try_into().unwrap());
                        w.set_name(ids.id(&wire_data.name).val().try_into().unwrap());
                        {
                            let mut bel_pins = w.reborrow().init_pins(wire_data.bel_pins.len().try_into().unwrap());
                            for (k, pin) in wire_data.bel_pins.iter().enumerate() {
                                bel_pins.set(k.try_into().unwrap(), (*pin).try_into().unwrap());
                            }
                        }
                    }
                }
                {
                    let mut pips = st.reborrow().init_site_p_i_ps(data.pips.len().try_into().unwrap());
                    for (j, pip_data) in data.pips.iter().enumerate() {
                        let mut p = pips.reborrow().get(j.try_into().unwrap());
                        p.set_inpin(pip_data.in_pin.try_into().unwrap());
                        p.set_outpin(pip_data.out_pin.try_into().unwrap());
                    }
                }
                {
                    let mut pins = st.reborrow().init_pins(data.pins.len().try_into().unwrap());
                    for (j, pin_data) in data.pins.iter().enumerate() {
                        let mut p = pins.reborrow().get(j.try_into().unwrap());
                        p.set_name(ids.id(&pin_data.site_wire).val().try_into().unwrap());
                        p.set_dir(match pin_data.dir {
                            PinDir::INPUT => LogicalNetlist_capnp::netlist::Direction::Input,
                            PinDir::OUTPUT => LogicalNetlist_capnp::netlist::Direction::Output,
                            PinDir::INOUT => LogicalNetlist_capnp::netlist::Direction::Inout,
                        });
                        p.set_belpin(pin_data.bel_pin.try_into().unwrap());
                    }
                }
            }
        }
        let mut wire_list = Vec::new();
        {
            // this wire_list is the list of wires as we create nodes, to be output as dev.wires later
            let mut nodes = dev.reborrow().init_nodes(graph.nodes.len().try_into().unwrap());
            for (i, node_data) in graph.nodes.iter().enumerate() {
                let n = nodes.reborrow().get(i.try_into().unwrap());
                let mut node_wires = n.init_wires(node_data.wires.len().try_into().unwrap());
                // write root wire
                {
                    node_wires.set(0, wire_list.len().try_into().unwrap());
                    wire_list.push((node_data.root_wire.tile_name, node_data.root_wire.wire_name));
                }
                let mut node_wire_idx = 1;
                // write non-root wires
                for wire in node_data.wires.iter().filter(|w| **w != node_data.root_wire) {
                    node_wires.set(node_wire_idx, wire_list.len().try_into().unwrap());
                    wire_list.push((wire.tile_name, wire.wire_name));
                    node_wire_idx += 1;
                }
            }
        }
        {
            let mut wires = dev.reborrow().init_wires(wire_list.len().try_into().unwrap());
            for (i, (tile_name, wire_name)) in wire_list.iter().enumerate() {
                let mut w = wires.reborrow().get(i.try_into().unwrap());
                w.set_tile(tile_name.val().try_into().unwrap());
                w.set_wire(wire_name.val().try_into().unwrap());
            }
        }
        {
            let mut tiles = dev.reborrow().init_tile_list(graph.tiles.len().try_into().unwrap());
            for (i, tile_data) in graph.tiles.iter().enumerate() {
                let mut t = tiles.reborrow().get(i.try_into().unwrap());
                t.set_name(tile_data.name.val().try_into().unwrap());
                t.set_type(tile_data.type_idx.try_into().unwrap());
                t.set_row(tile_data.y.try_into().unwrap());
                t.set_col(tile_data.x.try_into().unwrap());
                {
                    let tt = graph.tile_types.value(tile_data.type_idx);
                    let mut sites = t.reborrow().init_sites(tt.site_types.len().try_into().unwrap());
                    for (j, site_data) in tt.site_types.iter().enumerate() {
                        let mut s = sites.reborrow().get(j.try_into().unwrap());
                        s.set_name(ids.id(&format!("R{}C{}_{}", tile_data.y, tile_data.x, &site_data.name)).val().try_into().unwrap());
                        s.set_type(j.try_into().unwrap());
                    }
                }
            }
        }
        {
            let mut constants = dev.reborrow().init_constants();
            constants.set_gnd_cell_type(ids.id("VLO").val().try_into().unwrap());
            constants.set_gnd_cell_pin(ids.id("Z").val().try_into().unwrap());
            constants.set_vcc_cell_type(ids.id("VHI").val().try_into().unwrap());
            constants.set_vcc_cell_pin(ids.id("Z").val().try_into().unwrap());
        }
        {
            let mut constraints = dev.reborrow().init_constraints();
            {
                let mut cc = constraints.reborrow().init_cell_constraints(1);
                {
                    let mut lut_constr = cc.reborrow().get(0);
                    lut_constr.set_cell("LUT4");
                    let mut lut_loc = lut_constr.init_locations(1).get(0);
                    lut_loc.reborrow().init_site_types(1).set(0, "PLC");
                    {
                        let mut lut_bels = lut_loc.reborrow().init_bel().init_bels(8);
                        for i in 0..8 {
                            lut_bels.set(i,
                                &format!("SLICE{}_LUT{}", "ABCD".chars().nth((i / 2) as usize).unwrap(), i % 2));
                        }
                    }
                    lut_loc.reborrow().init_implies(0);
                }
            }
        }
        {
            let mut pin_maps = Vec::new();
            for (site_type, site) in uniq_site_types.iter() {
                pin_maps.extend(get_pin_maps(site).drain(..).map(|x| (site_type.to_string(), x)));
            }
            let mut c2b = dev.reborrow().init_cell_bel_map((2 + pin_maps.len()).try_into().unwrap());
            c2b.reborrow().get(0).set_cell(ids.id("VLO").val().try_into().unwrap());
            c2b.reborrow().get(1).set_cell(ids.id("VHI").val().try_into().unwrap());
            for (i, (site_type, pin_map_data)) in pin_maps.iter().enumerate() {
                let mut m = c2b.reborrow().get((2 + i).try_into().unwrap());
                m.set_cell(ids.id(&pin_map_data.cell_type).val().try_into().unwrap());
                let mut pin_map = m.init_common_pins(1).get(0);
                {
                    let mut st = pin_map.reborrow().init_site_types(1).get(0);
                    st.set_site_type(ids.id(&site_type).val().try_into().unwrap());
                    let mut bels = st.init_bels(pin_map_data.bels.len().try_into().unwrap());
                    for (j, bel) in pin_map_data.bels.iter().enumerate() {
                        bels.set(j.try_into().unwrap(), ids.id(&bel).val().try_into().unwrap());
                    }
                }
                {
                    let mut pins = pin_map.init_pins(pin_map_data.pin_map.len().try_into().unwrap());
                    for (j, (cell_pin, bel_pin)) in pin_map_data.pin_map.iter().enumerate() {
                        pins.reborrow().get(j.try_into().unwrap()).set_cell_pin(ids.id(cell_pin).val().try_into().unwrap());
                        pins.reborrow().get(j.try_into().unwrap()).set_bel_pin(ids.id(bel_pin).val().try_into().unwrap());
                    }
                }
            }

        }
        {
            let mut packages = dev.reborrow().init_packages(1);
            packages.reborrow().get(0).set_name(ids.id("QFN72").val().try_into().unwrap());
            // TODO: all packages and pin map
        }
        {
            let mut strs = dev.init_str_list(ids.len().try_into().unwrap());
            for i in 0..ids.len() {
                strs.set(i.try_into().unwrap(), ids.idx_str(i));
            }
        }
    }
    let mut e = GzEncoder::new(std::fs::File::create(filename)?, Compression::default());
    ::capnp::serialize::write_message(&mut e, &m)?;
    e.finish()?;
    Ok(())
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::activation::Activatable;
use dom::attr::AttrValue;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding;
use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods;
use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::InheritTypes::{HTMLAnchorElementDerived, HTMLImageElementDerived};
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::codegen::InheritTypes::{MouseEventCast, NodeCast};
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::document::{Document, DocumentHelpers};
use dom::domtokenlist::DOMTokenList;
use dom::element::{Element, AttributeHandlers, ElementTypeId};
use dom::event::Event;
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::window::WindowHelpers;

use num::ToPrimitive;
use std::default::Default;
use string_cache::Atom;
use util::str::DOMString;

#[dom_struct]
pub struct HTMLAnchorElement {
    htmlelement: HTMLElement,
    rel_list: MutNullableHeap<JS<DOMTokenList>>,
}

impl HTMLAnchorElementDerived for EventTarget {
    fn is_htmlanchorelement(&self) -> bool {
        *self.type_id() ==
            EventTargetTypeId::Node(
                NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)))
    }
}

impl HTMLAnchorElement {
    fn new_inherited(localName: DOMString,
                     prefix: Option<DOMString>,
                     document: &Document) -> HTMLAnchorElement {
        HTMLAnchorElement {
            htmlelement:
                HTMLElement::new_inherited(HTMLElementTypeId::HTMLAnchorElement, localName, prefix, document),
            rel_list: Default::default(),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(localName: DOMString,
               prefix: Option<DOMString>,
               document: &Document) -> Root<HTMLAnchorElement> {
        let element = HTMLAnchorElement::new_inherited(localName, prefix, document);
        Node::reflect_node(box element, document, HTMLAnchorElementBinding::Wrap)
    }
}

impl<'a> VirtualMethods for &'a HTMLAnchorElement {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let htmlelement: &&HTMLElement = HTMLElementCast::from_borrowed_ref(self);
        Some(htmlelement as &VirtualMethods)
    }

    fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
        match name {
            &atom!("rel") => AttrValue::from_serialized_tokenlist(value),
            _ => self.super_type().unwrap().parse_plain_attribute(name, value),
        }
    }
}

impl<'a> HTMLAnchorElementMethods for &'a HTMLAnchorElement {
    fn Text(self) -> DOMString {
        let node = NodeCast::from_ref(self);
        node.GetTextContent().unwrap()
    }

    fn SetText(self, value: DOMString) {
        let node = NodeCast::from_ref(self);
        node.SetTextContent(Some(value))
    }

    fn RelList(self) -> Root<DOMTokenList> {
        self.rel_list.or_init(|| {
            DOMTokenList::new(ElementCast::from_ref(self), &atom!("rel"))
        })
    }
}

impl<'a> Activatable for &'a HTMLAnchorElement {
    fn as_element<'b>(&'b self) -> &'b Element {
        ElementCast::from_ref(*self)
    }

    fn is_instance_activatable(&self) -> bool {
        true
    }

    //TODO:https://html.spec.whatwg.org/multipage/#the-a-element
    fn pre_click_activation(&self) {
    }

    //TODO:https://html.spec.whatwg.org/multipage/#the-a-element
    // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps
    fn canceled_activation(&self) {
    }

    //https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour
    fn activation_behavior(&self, event: &Event, target: &EventTarget) {
        //Step 1. If the node document is not fully active, abort.
        let doc = document_from_node(*self);
        if !doc.r().is_fully_active() {
            return;
        }
        //TODO: Step 2. Check if browsing context is specified and act accordingly.
        //Step 3. Handle <img ismap/>.
        let element = ElementCast::from_ref(*self);
        let mouse_event = MouseEventCast::to_ref(event).unwrap();
        let mut ismap_suffix = None;
        if let Some(element) = ElementCast::to_ref(target) {
            if target.is_htmlimageelement() && element.has_attribute(&atom!("ismap")) {

                let target_node = NodeCast::from_ref(element);
                let rect = window_from_node(target_node).r().content_box_query(
                    target_node.to_trusted_node_address());
                ismap_suffix = Some(
                    format!("?{},{}", mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
                                      mouse_event.ClientY().to_f32().unwrap() - rect.origin.y.to_f32_px())
                )
            }
        }

        //TODO: Step 4. Download the link is `download` attribute is set.

        if let Some(ref href) = element.get_attribute(&ns!(""), &atom!("href")) {
            let mut value = href.r().Value();
            if let Some(suffix) = ismap_suffix {
                value.push_str(&suffix);
            }
            debug!("clicked on link to {}", value);
            doc.r().load_anchor_href(value);
        }
    }

    //TODO:https://html.spec.whatwg.org/multipage/#the-a-element
    fn implicit_submission(&self, _ctrlKey: bool, _shiftKey: bool, _altKey: bool, _metaKey: bool) {
    }
}
<commit_msg>Auto merge of #6543 - Ms2ger:a-href-activate, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::activation::Activatable;
use dom::attr::AttrValue;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding;
use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods;
use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::InheritTypes::{HTMLAnchorElementDerived, HTMLImageElementDerived};
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::codegen::InheritTypes::{MouseEventCast, NodeCast};
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::document::{Document, DocumentHelpers};
use dom::domtokenlist::DOMTokenList;
use dom::element::{Element, AttributeHandlers, ElementTypeId};
use dom::event::Event;
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::window::WindowHelpers;

use num::ToPrimitive;
use std::default::Default;
use string_cache::Atom;
use util::str::DOMString;

#[dom_struct]
pub struct HTMLAnchorElement {
    htmlelement: HTMLElement,
    rel_list: MutNullableHeap<JS<DOMTokenList>>,
}

impl HTMLAnchorElementDerived for EventTarget {
    fn is_htmlanchorelement(&self) -> bool {
        *self.type_id() ==
            EventTargetTypeId::Node(
                NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)))
    }
}

impl HTMLAnchorElement {
    fn new_inherited(localName: DOMString,
                     prefix: Option<DOMString>,
                     document: &Document) -> HTMLAnchorElement {
        HTMLAnchorElement {
            htmlelement:
                HTMLElement::new_inherited(HTMLElementTypeId::HTMLAnchorElement, localName, prefix, document),
            rel_list: Default::default(),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(localName: DOMString,
               prefix: Option<DOMString>,
               document: &Document) -> Root<HTMLAnchorElement> {
        let element = HTMLAnchorElement::new_inherited(localName, prefix, document);
        Node::reflect_node(box element, document, HTMLAnchorElementBinding::Wrap)
    }
}

impl<'a> VirtualMethods for &'a HTMLAnchorElement {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let htmlelement: &&HTMLElement = HTMLElementCast::from_borrowed_ref(self);
        Some(htmlelement as &VirtualMethods)
    }

    fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
        match name {
            &atom!("rel") => AttrValue::from_serialized_tokenlist(value),
            _ => self.super_type().unwrap().parse_plain_attribute(name, value),
        }
    }
}

impl<'a> HTMLAnchorElementMethods for &'a HTMLAnchorElement {
    fn Text(self) -> DOMString {
        let node = NodeCast::from_ref(self);
        node.GetTextContent().unwrap()
    }

    fn SetText(self, value: DOMString) {
        let node = NodeCast::from_ref(self);
        node.SetTextContent(Some(value))
    }

    fn RelList(self) -> Root<DOMTokenList> {
        self.rel_list.or_init(|| {
            DOMTokenList::new(ElementCast::from_ref(self), &atom!("rel"))
        })
    }
}

impl<'a> Activatable for &'a HTMLAnchorElement {
    fn as_element<'b>(&'b self) -> &'b Element {
        ElementCast::from_ref(*self)
    }

    fn is_instance_activatable(&self) -> bool {
        // https://html.spec.whatwg.org/multipage/#hyperlink
        // "a [...] element[s] with an href attribute [...] must [..] create a
        // hyperlink"
        // https://html.spec.whatwg.org/multipage/#the-a-element
        // "The activation behaviour of a elements *that create hyperlinks*"
        ElementCast::from_ref(*self).has_attribute(&atom!("href"))
    }

    //TODO:https://html.spec.whatwg.org/multipage/#the-a-element
    fn pre_click_activation(&self) {
    }

    //TODO:https://html.spec.whatwg.org/multipage/#the-a-element
    // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps
    fn canceled_activation(&self) {
    }

    //https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour
    fn activation_behavior(&self, event: &Event, target: &EventTarget) {
        //Step 1. If the node document is not fully active, abort.
        let doc = document_from_node(*self);
        if !doc.r().is_fully_active() {
            return;
        }
        //TODO: Step 2. Check if browsing context is specified and act accordingly.
        //Step 3. Handle <img ismap/>.
        let element = ElementCast::from_ref(*self);
        let mouse_event = MouseEventCast::to_ref(event).unwrap();
        let mut ismap_suffix = None;
        if let Some(element) = ElementCast::to_ref(target) {
            if target.is_htmlimageelement() && element.has_attribute(&atom!("ismap")) {

                let target_node = NodeCast::from_ref(element);
                let rect = window_from_node(target_node).r().content_box_query(
                    target_node.to_trusted_node_address());
                ismap_suffix = Some(
                    format!("?{},{}", mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
                                      mouse_event.ClientY().to_f32().unwrap() - rect.origin.y.to_f32_px())
                )
            }
        }

        //TODO: Step 4. Download the link is `download` attribute is set.

        let href = element.get_attribute(&ns!(""), &atom!("href")).unwrap();
        let mut value = href.r().Value();
        if let Some(suffix) = ismap_suffix {
            value.push_str(&suffix);
        }
        debug!("clicked on link to {}", value);
        doc.r().load_anchor_href(value);
    }

    //TODO:https://html.spec.whatwg.org/multipage/#the-a-element
    fn implicit_submission(&self, _ctrlKey: bool, _shiftKey: bool, _altKey: bool, _metaKey: bool) {
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use canvas_traits::webgl::{webgl_channel, WebGLCommand, WebGLError, WebGLRenderbufferId, WebGLResult};
use dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as WebGl2Constants;
use dom::bindings::codegen::Bindings::WebGLRenderbufferBinding;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::webglobject::WebGLObject;
use dom::webglrenderingcontext::{WebGLRenderingContext, is_gles};
use dom_struct::dom_struct;
use std::cell::Cell;

#[dom_struct]
pub struct WebGLRenderbuffer {
    webgl_object: WebGLObject,
    id: WebGLRenderbufferId,
    ever_bound: Cell<bool>,
    is_deleted: Cell<bool>,
    size: Cell<Option<(i32, i32)>>,
    internal_format: Cell<Option<u32>>,
    is_initialized: Cell<bool>,
}

impl WebGLRenderbuffer {
    fn new_inherited(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> Self {
        Self {
            webgl_object: WebGLObject::new_inherited(context),
            id: id,
            ever_bound: Cell::new(false),
            is_deleted: Cell::new(false),
            internal_format: Cell::new(None),
            size: Cell::new(None),
            is_initialized: Cell::new(false),
        }
    }

    pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> {
        let (sender, receiver) = webgl_channel().unwrap();
        context.send_command(WebGLCommand::CreateRenderbuffer(sender));
        receiver
            .recv()
            .unwrap()
            .map(|id| WebGLRenderbuffer::new(context, id))
    }

    pub fn new(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> DomRoot<Self> {
        reflect_dom_object(
            Box::new(WebGLRenderbuffer::new_inherited(context, id)),
            &*context.global(),
            WebGLRenderbufferBinding::Wrap,
        )
    }
}

impl WebGLRenderbuffer {
    pub fn id(&self) -> WebGLRenderbufferId {
        self.id
    }

    pub fn size(&self) -> Option<(i32, i32)> {
        self.size.get()
    }

    pub fn internal_format(&self) -> u32 {
        self.internal_format.get().unwrap_or(constants::RGBA4)
    }

    pub fn mark_initialized(&self) {
        self.is_initialized.set(true);
    }

    pub fn is_initialized(&self) -> bool {
        self.is_initialized.get()
    }

    pub fn bind(&self, target: u32) {
        self.ever_bound.set(true);
        self.upcast::<WebGLObject>()
            .context()
            .send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id)));
    }

    pub fn delete(&self) {
        if !self.is_deleted.get() {
            self.is_deleted.set(true);

            /*
            If a renderbuffer object is deleted while its image is attached to the currently
            bound framebuffer, then it is as if FramebufferRenderbuffer had been called, with
            a renderbuffer of 0, for each attachment point to which this image was attached
            in the currently bound framebuffer.
            - GLES 2.0, 4.4.3, "Attaching Renderbuffer Images to a Framebuffer"
             */
            let currently_bound_framebuffer =
                self.upcast::<WebGLObject>().context().bound_framebuffer();
            if let Some(fb) = currently_bound_framebuffer {
                fb.detach_renderbuffer(self);
            }

            self.upcast::<WebGLObject>()
                .context()
                .send_command(WebGLCommand::DeleteRenderbuffer(self.id));
        }
    }

    pub fn is_deleted(&self) -> bool {
        self.is_deleted.get()
    }

    pub fn ever_bound(&self) -> bool {
        self.ever_bound.get()
    }

    pub fn storage(&self, internal_format: u32, width: i32, height: i32) -> WebGLResult<()> {
        // Validate the internal_format, and save it for completeness
        // validation.
        let actual_format = match internal_format {
            constants::RGBA4 |
            constants::DEPTH_COMPONENT16 |
            constants::STENCIL_INDEX8 |
            // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.7
            constants::DEPTH_STENCIL => internal_format,
            constants::RGB5_A1 => {
                // 16-bit RGBA formats are not supported on desktop GL.
                if is_gles() {
                    constants::RGB5_A1
                } else {
                    WebGl2Constants::RGBA8
                }
            }
            constants::RGB565 => {
                // RGB565 is not supported on desktop GL.
                if is_gles() {
                    constants::RGB565
                } else {
                    WebGl2Constants::RGB8
                }
            }
            _ => return Err(WebGLError::InvalidEnum),
        };

        self.internal_format.set(Some(internal_format));
        self.is_initialized.set(false);

        // FIXME: Invalidate completeness after the call

        self.upcast::<WebGLObject>()
            .context()
            .send_command(WebGLCommand::RenderbufferStorage(
                constants::RENDERBUFFER,
                actual_format,
                width,
                height,
            ));

        self.size.set(Some((width, height)));

        Ok(())
    }
}
<commit_msg>Auto merge of #21784 - servo:jdm-patch-33, r=nox<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use canvas_traits::webgl::{webgl_channel, WebGLCommand, WebGLError, WebGLRenderbufferId, WebGLResult};
use dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as WebGl2Constants;
use dom::bindings::codegen::Bindings::WebGLRenderbufferBinding;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::webglobject::WebGLObject;
use dom::webglrenderingcontext::{WebGLRenderingContext, is_gles};
use dom_struct::dom_struct;
use std::cell::Cell;

#[dom_struct]
pub struct WebGLRenderbuffer {
    webgl_object: WebGLObject,
    id: WebGLRenderbufferId,
    ever_bound: Cell<bool>,
    is_deleted: Cell<bool>,
    size: Cell<Option<(i32, i32)>>,
    internal_format: Cell<Option<u32>>,
    is_initialized: Cell<bool>,
}

impl WebGLRenderbuffer {
    fn new_inherited(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> Self {
        Self {
            webgl_object: WebGLObject::new_inherited(context),
            id: id,
            ever_bound: Cell::new(false),
            is_deleted: Cell::new(false),
            internal_format: Cell::new(None),
            size: Cell::new(None),
            is_initialized: Cell::new(false),
        }
    }

    pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> {
        let (sender, receiver) = webgl_channel().unwrap();
        context.send_command(WebGLCommand::CreateRenderbuffer(sender));
        receiver
            .recv()
            .unwrap()
            .map(|id| WebGLRenderbuffer::new(context, id))
    }

    pub fn new(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> DomRoot<Self> {
        reflect_dom_object(
            Box::new(WebGLRenderbuffer::new_inherited(context, id)),
            &*context.global(),
            WebGLRenderbufferBinding::Wrap,
        )
    }
}

impl WebGLRenderbuffer {
    pub fn id(&self) -> WebGLRenderbufferId {
        self.id
    }

    pub fn size(&self) -> Option<(i32, i32)> {
        self.size.get()
    }

    pub fn internal_format(&self) -> u32 {
        self.internal_format.get().unwrap_or(constants::RGBA4)
    }

    pub fn mark_initialized(&self) {
        self.is_initialized.set(true);
    }

    pub fn is_initialized(&self) -> bool {
        self.is_initialized.get()
    }

    pub fn bind(&self, target: u32) {
        self.ever_bound.set(true);
        self.upcast::<WebGLObject>()
            .context()
            .send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id)));
    }

    pub fn delete(&self) {
        if !self.is_deleted.get() {
            self.is_deleted.set(true);

            /*
            If a renderbuffer object is deleted while its image is attached to the currently
            bound framebuffer, then it is as if FramebufferRenderbuffer had been called, with
            a renderbuffer of 0, for each attachment point to which this image was attached
            in the currently bound framebuffer.
            - GLES 2.0, 4.4.3, "Attaching Renderbuffer Images to a Framebuffer"
             */
            let currently_bound_framebuffer =
                self.upcast::<WebGLObject>().context().bound_framebuffer();
            if let Some(fb) = currently_bound_framebuffer {
                fb.detach_renderbuffer(self);
            }

            self.upcast::<WebGLObject>()
                .context()
                .send_command(WebGLCommand::DeleteRenderbuffer(self.id));
        }
    }

    pub fn is_deleted(&self) -> bool {
        self.is_deleted.get()
    }

    pub fn ever_bound(&self) -> bool {
        self.ever_bound.get()
    }

    pub fn storage(&self, internal_format: u32, width: i32, height: i32) -> WebGLResult<()> {
        // Validate the internal_format, and save it for completeness
        // validation.
        let actual_format = match internal_format {
            constants::RGBA4 |
            constants::DEPTH_COMPONENT16 |
            constants::STENCIL_INDEX8 => internal_format,
            // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.8
            constants::DEPTH_STENCIL => WebGl2Constants::DEPTH24_STENCIL8,
            constants::RGB5_A1 => {
                // 16-bit RGBA formats are not supported on desktop GL.
                if is_gles() {
                    constants::RGB5_A1
                } else {
                    WebGl2Constants::RGBA8
                }
            }
            constants::RGB565 => {
                // RGB565 is not supported on desktop GL.
                if is_gles() {
                    constants::RGB565
                } else {
                    WebGl2Constants::RGB8
                }
            }
            _ => return Err(WebGLError::InvalidEnum),
        };

        self.internal_format.set(Some(internal_format));
        self.is_initialized.set(false);

        // FIXME: Invalidate completeness after the call

        self.upcast::<WebGLObject>()
            .context()
            .send_command(WebGLCommand::RenderbufferStorage(
                constants::RENDERBUFFER,
                actual_format,
                width,
                height,
            ));

        self.size.set(Some((width, height)));

        Ok(())
    }
}
<|endoftext|>


<commit_before>use aes::Aes128;
use aes::BLOCK_SIZE;
use aes::MODE;

use xor::XOR;

use aes::random_block;

use errors::*;

struct Sender {
    key: Vec<u8>,
}

struct Receiver {
    key: Vec<u8>,
}

impl Sender {
    pub fn encrypt(&self, input: &[u8]) -> Result<Vec<u8>, Error> {
        // Exclude ';' and '='
        if input
            .iter()
            .any(|&c| !c.is_ascii() || c == b';' || c == b'=')
        {
            bail!("invalid character in input");
        }

        let prefix = b"comment1=cooking%20MCs;userdata=";
        let suffix = b";comment2=%20like%20a%20pound%20of%20bacon";
        let mut cleartext = Vec::with_capacity(prefix.len() + input.len() + suffix.len());
        cleartext.extend_from_slice(prefix);
        cleartext.extend_from_slice(input);
        cleartext.extend_from_slice(suffix);
        cleartext.encrypt(&self.key, Some(&self.key), MODE::CBC)
    }
}

impl Receiver {
    pub fn try_decrypt(&self, ciphertext: &[u8]) -> Result<(), Error> {
        let cleartext = ciphertext.decrypt(&self.key, Some(&self.key), MODE::CBC)?;
        if !cleartext.is_ascii() {
            Err(NonAscii(cleartext).into())
        } else {
            Ok(())
        }
    }

    pub fn verify_solution(&self, candidate_key: &[u8]) -> Result<(), Error> {
        compare_eq(&self.key[..], candidate_key)
    }
}

fn get_sender_and_receiver_with_shared_key() -> (Sender, Receiver) {
    let secret_key = random_block();
    let sender = Sender {
        key: secret_key.clone(),
    };
    let receiver = Receiver {
        key: secret_key.clone(),
    };
    (sender, receiver)
}

#[derive(Debug, Fail)]
#[fail(display = "invalid input: {:?}", _0)]
struct NonAscii(Vec<u8>);

pub fn run() -> Result<(), Error> {
    let (sender, receiver) = get_sender_and_receiver_with_shared_key();

    let ciphertext = sender.encrypt(&[])?;

    let mut attack_ciphertext = Vec::with_capacity(3 * BLOCK_SIZE);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&[0; BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    //Push last two blocks to preserve valid padding at the end
    attack_ciphertext.extend_from_slice(&ciphertext[ciphertext.len() - 2 * BLOCK_SIZE..]);

    if let Err(err) = receiver.try_decrypt(&attack_ciphertext) {
        if let Ok(NonAscii(attack_cleartext)) = err.downcast::<NonAscii>() {
            return receiver.verify_solution(
                &attack_cleartext[0..BLOCK_SIZE]
                    .xor(&attack_cleartext[2 * BLOCK_SIZE..3 * BLOCK_SIZE]),
            );
        }
    }

    bail!("attack ciphertext did not deceive the receiver");
}
<commit_msg>Simply and clarify challenge 27<commit_after>use aes::Aes128;
use aes::BLOCK_SIZE;
use aes::MODE;

use xor::XOR;

use aes::random_block;

use errors::*;

struct Sender {
    key: Vec<u8>,
}

struct Receiver {
    key: Vec<u8>,
}

impl Sender {
    pub fn get_ciphertext(&self) -> Result<Vec<u8>, Error> {
        let cleartext = b"comment1=cooking%20MCs;userdata=foo@baz.com;comment2=%20like%20a%20pound%20of%20bacon";
        cleartext.encrypt(&self.key, Some(&self.key), MODE::CBC)
    }
}

impl Receiver {
    pub fn try_decrypt(&self, ciphertext: &[u8]) -> Result<(), Error> {
        let cleartext = ciphertext.decrypt(&self.key, Some(&self.key), MODE::CBC)?;
        if !cleartext.is_ascii() {
            Err(NonAscii(cleartext).into())
        } else {
            Ok(())
        }
    }

    pub fn verify_solution(&self, candidate_key: &[u8]) -> Result<(), Error> {
        compare_eq(&self.key[..], candidate_key)
    }
}

fn get_sender_and_receiver_with_shared_key() -> (Sender, Receiver) {
    let secret_key = random_block();
    let sender = Sender {
        key: secret_key.clone(),
    };
    let receiver = Receiver {
        key: secret_key.clone(),
    };
    (sender, receiver)
}

#[derive(Debug, Fail)]
#[fail(display = "invalid input: {:?}", _0)]
struct NonAscii(Vec<u8>);

pub fn run() -> Result<(), Error> {
    let (sender, receiver) = get_sender_and_receiver_with_shared_key();
    let ciphertext = sender.get_ciphertext()?;

    ensure!(ciphertext.len() >= 3*BLOCK_SIZE, "ciphertext does not have expected length");

    // Let C_1 be the first block of `ciphertext` and let P_1 be the first block of the
    // cleartext behind `ciphertext` (under CBC). Let K be the unknown key. We know that
    // AES-ECB(C_1) = P_1 XOR K, as K was used as the IV.
    // Our attack ciphertext is C_1 || 0 || C_1. With CBC and K as IV, this will decrypt to
    // AES-ECB(C1) XOR K || * || AES-ECB(C1) XOR 0
    // = P_1 || * || P_1 XOR K.
    // We can therefore recover K by XORing the first and the third block of this cleartext.

    let mut attack_ciphertext = Vec::with_capacity(3 * BLOCK_SIZE);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&[0; BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);

    //Push last two blocks to preserve valid padding at the end
    attack_ciphertext.extend_from_slice(&ciphertext[ciphertext.len() - 2 * BLOCK_SIZE..]);

    if let Err(err) = receiver.try_decrypt(&attack_ciphertext) {
        if let Ok(NonAscii(cleartext)) = err.downcast::<NonAscii>() {
            return receiver.verify_solution(
                &cleartext[0..BLOCK_SIZE]
                    .xor(&cleartext[2 * BLOCK_SIZE..3 * BLOCK_SIZE]),
            );
        }
    }

    bail!("attack ciphertext did not deceive the receiver");
}
<|endoftext|>