001/*-
002 * #%L
003 * Smile CDR - CDR
004 * %%
005 * Copyright (C) 2016 - 2025 Smile CDR, Inc.
006 * %%
007 * All rights reserved.
008 * #L%
009 */
010package ca.cdr.api.security.permission;
011
012import jakarta.annotation.Nonnull;
013
014import java.util.Optional;
015
016import static org.apache.commons.lang3.StringUtils.isNotBlank;
017
018/**
019 * A role name.
020 * Accepts only uppercase letters and single underscores between letters.
021 */
022public final class RoleName extends PermissionArgumentValue implements PermissionArgumentFormat.IRoleNameRestriction {
023        // final for optimizer
024
025        private static final String ROLE_NAME_PATTERN = "^[A-Z]+(_[A-Z]+)*$";
026
027        private final String myRoleName;
028
029        RoleName(String theRoleName) {
030                myRoleName = theRoleName;
031        }
032
033        /**
034         * Validates that a role name contains only uppercase letters and single underscores between letters.
035         * <p>
036         * Valid examples: {@code MDM_UI_DATA_STEWARD}, {@code MY_ROLE}, {@code A}
037         * <p>
038         * Invalid examples: {@code myRole} (lowercase), {@code ROLE__SALES} (consecutive underscores),
039         * {@code _ADMIN} (leading underscore), {@code ADMIN_} (trailing underscore),
040         * {@code ROLE123} (numbers), {@code ROLE-ADMIN} (special characters)
041         *
042         * @param theRoleName the role name to validate
043         * @return true if the role name is valid, false otherwise
044         */
045        public static boolean isValidRoleName(String theRoleName) {
046                return isNotBlank(theRoleName) && theRoleName.matches(ROLE_NAME_PATTERN);
047        }
048
049        /**
050         * Validates that a role name contains only uppercase letters and single underscores between letters.
051         * Throws an exception if the role name is invalid.
052         *
053         * @param theRoleName the role name to validate
054         * @param theRoleType the type of role (e.g., "APP_ROLE", "CUSTOM_ROLE") for error messages
055         * @throws IllegalArgumentException if the role name is invalid
056         */
057        public static void validateRoleName(String theRoleName, String theRoleType) {
058                if (!isValidRoleName(theRoleName)) {
059                        throw new IllegalArgumentException(theRoleType
060                                        + " name must contain only uppercase letters and single underscores between letters. Invalid role name: "
061                                        + theRoleName);
062                }
063        }
064
065        @Override
066        public String getRoleName() {
067                return myRoleName;
068        }
069
070        @Override
071        @Nonnull
072        String getStringValue() {
073                return myRoleName;
074        }
075
076        static final class Format implements PermissionArgumentFormat<RoleName> {
077
078                @Override
079                public Class<RoleName> getType() {
080                        return RoleName.class;
081                }
082
083                @Override
084                public @Nonnull Optional<RoleName> parse(String theArgumentString, @Nonnull IdBuilder theIdBuilder) {
085                        if (isValidRoleName(theArgumentString)) {
086                                return Optional.of(new RoleName(theArgumentString));
087                        }
088
089                        return Optional.empty();
090                }
091
092                @Override
093                public @Nonnull String format(@Nonnull RoleName theArgument) {
094                        return theArgument.getStringValue();
095                }
096        }
097}