From 840c6dcc25db9dd6d72455e7603cd4e53e449227 Mon Sep 17 00:00:00 2001 From: Ekrem Seren Date: Tue, 3 Dec 2024 01:43:28 +0300 Subject: [PATCH] Add service tag analysis script (#55) --- .github/workflows/ci.yaml | 9 + .../cmp/services/accommodation/v1/info.proto | 2 + .../cmp/services/accommodation/v1/list.proto | 2 + .../services/accommodation/v1/search.proto | 2 + .../cmp/services/notification/v1/notify.proto | 1 + scripts/analyze-service-tags.sh | 259 ++++++++++++++++++ 6 files changed, 275 insertions(+) create mode 100755 scripts/analyze-service-tags.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 57e97982..447aa1ef 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -97,6 +97,15 @@ jobs: - name: Run proto dependency check run: scripts/dependency_checker.py --print-graph + # dependency check to catch if proto files were not updated correctly + analyze-service-tags: + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + - name: Run service tag analysis + run: scripts/analyze-service-tags.sh proto + # Check if we use FQPN (fully qualified package names) everywhere fqpn-check: runs-on: ubuntu-latest diff --git a/proto/cmp/services/accommodation/v1/info.proto b/proto/cmp/services/accommodation/v1/info.proto index 964a04ca..a289b9c2 100644 --- a/proto/cmp/services/accommodation/v1/info.proto +++ b/proto/cmp/services/accommodation/v1/info.proto @@ -39,6 +39,8 @@ message AccommodationProductInfoResponse { // ![Diagram](https://storage.googleapis.com/docs-cmp-files/diagrams/proto/cmp/services/accommodation/v1/info.proto.dot.xs.svg) // // [Open Message Diagram](https://storage.googleapis.com/docs-cmp-files/diagrams/proto/cmp/services/accommodation/v1/info.proto.dot.svg) +// +/// @custom:cmp-service type:product routing:p2p on-chain:false service AccommodationProductInfoService { // Returns product list for accommodation (properties) rpc AccommodationProductInfo(AccommodationProductInfoRequest) returns (AccommodationProductInfoResponse); diff --git a/proto/cmp/services/accommodation/v1/list.proto b/proto/cmp/services/accommodation/v1/list.proto index 646edb0d..27984497 100644 --- a/proto/cmp/services/accommodation/v1/list.proto +++ b/proto/cmp/services/accommodation/v1/list.proto @@ -31,6 +31,8 @@ message AccommodationProductListResponse { // ![Diagram](https://storage.googleapis.com/docs-cmp-files/diagrams/proto/cmp/services/accommodation/v1/list.proto.dot.xs.svg) // // [Open Message Diagram](https://storage.googleapis.com/docs-cmp-files/diagrams/proto/cmp/services/accommodation/v1/list.proto.dot.svg) +// +/// @custom:cmp-service type:product routing:p2p on-chain:false service AccommodationProductListService { // Returns product list for accommodation (properties) rpc AccommodationProductList(AccommodationProductListRequest) returns (AccommodationProductListResponse); diff --git a/proto/cmp/services/accommodation/v1/search.proto b/proto/cmp/services/accommodation/v1/search.proto index 7fcad4ab..82271736 100644 --- a/proto/cmp/services/accommodation/v1/search.proto +++ b/proto/cmp/services/accommodation/v1/search.proto @@ -117,6 +117,8 @@ message AccommodationSearchResponse { // ![Diagram](https://storage.googleapis.com/docs-cmp-files/diagrams/proto/cmp/services/accommodation/v1/search.proto.dot.xs.svg) // // [Open Message Diagram](https://storage.googleapis.com/docs-cmp-files/diagrams/proto/cmp/services/accommodation/v1/search.proto.dot.svg) +// +/// @custom:cmp-service type:product routing:p2p on-chain:false service AccommodationSearchService { // Accommodation Search method rpc AccommodationSearch(AccommodationSearchRequest) returns (AccommodationSearchResponse); diff --git a/proto/cmp/services/notification/v1/notify.proto b/proto/cmp/services/notification/v1/notify.proto index 6b75a7cb..5f88b6d2 100644 --- a/proto/cmp/services/notification/v1/notify.proto +++ b/proto/cmp/services/notification/v1/notify.proto @@ -16,6 +16,7 @@ message TokenExpired { cmp.types.v1.UUID mint_id = 2; } +/// @custom:cmp-service type:product routing:local on-chain:true service NotificationService { rpc TokenBoughtNotification(TokenBought) returns (google.protobuf.Empty); rpc TokenExpiredNotification(TokenExpired) returns (google.protobuf.Empty); diff --git a/scripts/analyze-service-tags.sh b/scripts/analyze-service-tags.sh new file mode 100755 index 00000000..51ca2204 --- /dev/null +++ b/scripts/analyze-service-tags.sh @@ -0,0 +1,259 @@ +#!/bin/bash + +# Colors for output +RED='\033[0;31m' +LRED='\033[1;31m' +YELLOW='\033[0;33m' +LYELLOW='\033[1;33m' +GREEN='\033[0;32m' +LGREEN='\033[1;32m' +BLUE='\033[0;34m' +LBLUE='\033[1;34m' +CYAN='\033[0;36m' +LCYAN='\033[1;36m' +PURPLE='\033[0;35m' +LPURPLE='\033[1;35m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# Emojis +WARNING="đŸšĢ" +ERROR="❌" +SUCCESS="✅" +INFO="ℹī¸ " +FOLDER="📁" +MAGNIFIER="🔍" + +# Valid values +valid_types=("core" "product" "system") +valid_routing=("p2p" "local") +valid_onchain=("true" "false") + +# Initialize arrays for different categories +declare -a no_tag_services +declare -a invalid_tag_services +declare -a valid_tag_services + +check_service() { + local file="$1" + local service_name="$2" + local tag="$3" + local filepath="$4" + + # If no tag is present + if [ -z "$tag" ]; then + no_tag_services+=("$filepath:$service_name") + return + fi + + # First, check basic format (more permissive) + if ! [[ $tag =~ ^@custom:cmp-service[[:space:]]+type:([[:alnum:]]+)[[:space:]]+routing:([[:alnum:]]+)([[:space:]]+on-chain:([[:alnum:]]+))?$ ]]; then + invalid_tag_services+=("$filepath:$service_name|$tag - Malformed tag format: must follow '@custom:cmp-service type: routing: [on-chain:]'") + return + fi + + # Extract values + type="${BASH_REMATCH[1]}" + routing="${BASH_REMATCH[2]}" + onchain="${BASH_REMATCH[4]:-false}" + + # Validate type + if [[ ! " ${valid_types[@]} " =~ " ${type} " ]]; then + invalid_tag_services+=("$filepath:$service_name|$tag - Invalid type: $type") + return + fi + + # Validate routing + if [[ ! " ${valid_routing[@]} " =~ " ${routing} " ]]; then + invalid_tag_services+=("$filepath:$service_name|$tag - Invalid routing: $routing") + return + fi + + # Validate on-chain if present + if [ ! -z "$onchain" ] && [[ ! " ${valid_onchain[@]} " =~ " ${onchain} " ]]; then + invalid_tag_services+=("$filepath:$service_name|$tag - Invalid on-chain value: $onchain") + return + fi + + # If we get here, the tag is valid + valid_tag_services+=("$filepath:$service_name|$tag") +} + +scan_proto_files() { + local dir="$1" + + echo -e "${BLUE}${MAGNIFIER} Scanning directory: ${CYAN}$dir${NC}" + echo "----------------------------------------" + + # Find all .proto files recursively + while IFS= read -r -d '' file; do + while IFS= read -r line; do + # Check for service definition + if [[ $line =~ ^[[:space:]]*service[[:space:]]+([[:alnum:]]+)[[:space:]]*\{ ]]; then + service_name="${BASH_REMATCH[1]}" + # Get the line before service definition + prev_line=$(grep -B 1 "^[[:space:]]*service[[:space:]]\+${service_name}[[:space:]]*{" "$file" | head -n 1) + + # Check if previous line contains the custom tag + if [[ $prev_line =~ ///[[:space:]]*(@custom:cmp-service.*) ]]; then + check_service "$file" "$service_name" "${BASH_REMATCH[1]}" "$file" + else + check_service "$file" "$service_name" "" "$file" + fi + fi + done <"$file" + done < <(find "$dir" -type f -name "*.proto" -print0) +} + +colorize_tag() { + local tag="$1" + + # Color mappings for different values + local TYPE_CORE="${LPURPLE}" + local TYPE_PRODUCT="${LBLUE}" + local TYPE_SYSTEM="${LRED}" + + local ROUTING_P2P="${LGREEN}" + local ROUTING_LOCAL="${LYELLOW}" + + local ONCHAIN_TRUE="${LPURPLE}" + local ONCHAIN_FALSE="${LCYAN}" + + # Extract each part using regex + if [[ $tag =~ ^(@custom:cmp-service)[[:space:]]+(type:([[:alnum:]]+))[[:space:]]+(routing:([[:alnum:]]+))([[:space:]]+on-chain:([[:alnum:]]+))?$ ]]; then + local prefix="${BASH_REMATCH[1]}" + local type_full="${BASH_REMATCH[2]}" + local type_value="${BASH_REMATCH[3]}" + local routing_full="${BASH_REMATCH[4]}" + local routing_value="${BASH_REMATCH[5]}" + local onchain_full="${BASH_REMATCH[6]}" + local onchain_value="${BASH_REMATCH[7]}" + + # Select color for type + local type_color + case "$type_value" in + "core") type_color="$TYPE_CORE" ;; + "product") type_color="$TYPE_PRODUCT" ;; + "system") type_color="$TYPE_SYSTEM" ;; + *) type_color="$RED" ;; # Invalid value + esac + + # Select color for routing + local routing_color + case "$routing_value" in + "p2p") routing_color="$ROUTING_P2P" ;; + "local") routing_color="$ROUTING_LOCAL" ;; + *) routing_color="$RED" ;; # Invalid value + esac + + # Select color for on-chain + local onchain_color + case "$onchain_value" in + "true") onchain_color="$ONCHAIN_TRUE" ;; + "false") onchain_color="$ONCHAIN_FALSE" ;; + *) onchain_color="$RED" ;; # Invalid value + esac + + # Build the colored string + local result="${BOLD}$prefix${NC} ${type_color}${type_full}${NC} ${routing_color}${routing_full}${NC}" + #local result="$prefix type:${type_color}${type_value}${NC} routing:${routing_color}${routing_value}${NC}" + if [ ! -z "$onchain_full" ]; then + #result+=" on-chain:${onchain_color}${onchain_value}${NC}" + result+=" ${onchain_color}on-chain:${onchain_value}${NC}" + fi + + echo "$result" + else + # If the tag doesn't match the expected format, return it unchanged + echo "$tag" + fi +} + +format_service_output() { + local input="$1" + local filepath="${input%%:*}" + local rest="${input#*:}" + + # Check if the input contains a tag (has a | separator) + if [[ "$rest" == *"|"* ]]; then + local service="${rest%%|*}" + local tag="${rest#*|}" + + # For valid services, don't try to extract error message + if [[ "$tag" =~ .*" - ".* ]]; then + local error_msg=" - ${tag#* - }" + local clean_tag="${tag%% - *}" + echo -e " ${CYAN}${filepath}${NC}" + echo -e " └─ ${BOLD}$service${NC}" + echo -e " ${YELLOW}${clean_tag}${NC} ${RED}${error_msg}${NC}" + else + echo -e " ${CYAN}${filepath}${NC}" + echo -e " └─ ${BOLD}$service${NC}" + #echo -e " ${GREEN}${tag}${NC} ${SUCCESS}" + echo -e " $(colorize_tag "$tag")" + fi + else + # No tag case + local service="$rest" + echo -e " ${CYAN}${filepath}${NC}" + echo -e " └─ ${BOLD}$service${NC}" + fi +} + +# Main execution +if [ "$#" -ne 1 ]; then + echo -e "${ERROR} Usage: $0 " + exit 1 +fi + +if [ ! -d "$1" ]; then + echo -e "${ERROR} Error: Directory $1 does not exist" + exit 1 +fi + +# Scan the directory +scan_proto_files "$1" + +# Print results +echo -e "\n${RED}${ERROR} Services without @custom:cmp-service tag:${NC}" +echo "=================================================" +if [ ${#no_tag_services[@]} -eq 0 ]; then + echo -e " ${INFO} No services found without tags" +else + for service in "${no_tag_services[@]}"; do + format_service_output "$service" + done + EXIT_CODE=1 +fi + +echo -e "\n${BYELLOW}${WARNING} Services with invalid @custom:cmp-service tag:${NC}" +echo "=================================================" +if [ ${#invalid_tag_services[@]} -eq 0 ]; then + echo -e " ${INFO} No services found with invalid tags" +else + for service in "${invalid_tag_services[@]}"; do + format_service_output "$service" + done + + echo -e "\n ${INFO} Valid values: type=\"${valid_types[@]}\" routing=\"${valid_routing[@]}\" on-chain=\"${valid_onchain[@]}\"" + EXIT_CODE=1 +fi + +echo -e "\n${LGREEN}${SUCCESS} Services with valid @custom:cmp-service tag:${NC}" +echo "=================================================" +if [ ${#valid_tag_services[@]} -eq 0 ]; then + echo -e " ${INFO} No services found with valid tags" +else + for service in "${valid_tag_services[@]}"; do + format_service_output "$service" + done +fi + +# Print summary +echo -e "\n${BLUE}${INFO} Summary:${NC}" +echo "=================================================" +echo -e "${RED}${ERROR} Missing tags: ${#no_tag_services[@]}${NC}" +echo -e "${YELLOW}${WARNING} Invalid tags: ${#invalid_tag_services[@]}${NC}" +echo -e "${GREEN}${SUCCESS} Valid tags: ${#valid_tag_services[@]}${NC}" +echo -e "Total services: $((${#no_tag_services[@]} + ${#invalid_tag_services[@]} + ${#valid_tag_services[@]}))" +exit $EXIT_CODE