import json import pytest from xml.etree.ElementTree import canonicalize from guardrails_ai.types import Validator as ValidatorReference from guardrails.classes.schema.processed_schema import ProcessedSchema from guardrails.schema.rail_schema import ( rail_file_to_schema, json_schema_to_rail_output, ) from guardrails.classes.output_type import OutputTypes from guardrails.validator_base import OnFailAction from tests.integration_tests.test_assets.validators import ( ValidChoices, LowerCase, OneLine, TwoWords, ) ### JSON Schemas ### with open( "tests/integration_tests/test_assets/json_schemas/choice_case.json", "r" ) as choice_case_json_file: choice_case_json_schema = json.loads(choice_case_json_file.read()) with open( "tests/integration_tests/test_assets/json_schemas/choice_case_openapi.json", "r" ) as choice_case_openapi_file: choice_case_openapi_schema = json.loads(choice_case_openapi_file.read()) with open( "tests/integration_tests/test_assets/json_schemas/credit_card_agreement.json", "r" ) as credit_card_agreement_file: credit_card_agreement_schema = json.loads(credit_card_agreement_file.read()) with open( "tests/integration_tests/test_assets/json_schemas/string.json", "r" ) as string_file: string_schema = json.loads(string_file.read()) class TestRailToJsonSchema: # Did this one first because it's what I was most concerned about def test_choice_case_happy_path(self): from tests.integration_tests.test_assets.validators.valid_choices import ( ValidChoices, ) processed_schema: ProcessedSchema = rail_file_to_schema( "tests/integration_tests/test_assets/rail_specs/choice_case.rail" ) assert processed_schema.json_schema == choice_case_json_schema assert processed_schema.output_type == OutputTypes.DICT assert processed_schema.output_type == "dict" assert processed_schema.validators == [ ValidatorReference( id="valid-choices", on="$.action.weapon", on_fail=OnFailAction.REASK, kwargs={"choices": ["crossbow", "machine gun"]}, ), ValidatorReference( id="valid-choices", on="$.action.flight_direction", on_fail=OnFailAction.EXCEPTION, kwargs={"choices": ["north", "south", "east", "west"]}, ), ValidatorReference( id="valid-choices", on="$.action.distance", on_fail=OnFailAction.EXCEPTION, kwargs={"choices": [1, 2, 3, 4]}, ), ] assert len(processed_schema.validator_map) == 3 assert processed_schema.validator_map.get("$.action.distance") == [ ValidChoices(choices=[1, 2, 3, 4], on_fail=OnFailAction.EXCEPTION) ] assert processed_schema.validator_map.get("$.action.flight_direction") == [ ValidChoices( choices=["north", "south", "east", "west"], on_fail=OnFailAction.EXCEPTION, ) ] assert processed_schema.validator_map.get("$.action.weapon") == [ ValidChoices( choices=["crossbow", "machine gun"], on_fail=OnFailAction.REASK ) ] ### ReConstructed RAIL Specs for Prompting ### case_choice_rail = """ """.strip() # noqa # flight_direction.required is true here because Pydantic compiles optional properties # as a Union of the actual type and null but still marks it as required... case_choice_openapi_rail = """ """.strip() # noqa credit_card_agreement_rail = """ """.strip() # noqa string_schema_rail = """ """.strip() # noqa ### Validator Maps ### case_choice_validator_map = { "$.action.weapon": [ValidChoices(["crossbow", "machine gun"], OnFailAction.REASK)], "$.action.flight_direction": [ ValidChoices(["north", "south", "east", "west"], OnFailAction.EXCEPTION) ], "$.action.distance": [ValidChoices([1, 2, 3, 4], OnFailAction.EXCEPTION)], } credit_card_agreement_validator_map = { "$.fees.name": [LowerCase(), TwoWords()], "$.fees.explanation": [OneLine()], } @pytest.mark.parametrize( "json_schema,validator_map,rail_output", [ (choice_case_json_schema, case_choice_validator_map, case_choice_rail), ( choice_case_openapi_schema, case_choice_validator_map, case_choice_openapi_rail, ), ( credit_card_agreement_schema, credit_card_agreement_validator_map, credit_card_agreement_rail, ), ], ) def test_json_schema_to_rail_output(json_schema, validator_map, rail_output): actual_rail_output = json_schema_to_rail_output(json_schema, validator_map) actual_rail_xml = canonicalize(actual_rail_output) expected_rail_xml = canonicalize(rail_output) assert actual_rail_xml == expected_rail_xml